diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /external/vpc/tier0 | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'external/vpc/tier0')
34 files changed, 20527 insertions, 0 deletions
diff --git a/external/vpc/tier0/assert_dialog.cpp b/external/vpc/tier0/assert_dialog.cpp new file mode 100644 index 0000000..8df5e02 --- /dev/null +++ b/external/vpc/tier0/assert_dialog.cpp @@ -0,0 +1,573 @@ +//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#include "tier0/platform.h" + +#include "tier0/valve_off.h" +#ifdef _X360 +#include "xbox/xbox_console.h" +#include "xbox/xbox_vxconsole.h" +#elif defined( _PS3 ) +#include "ps3/ps3_console.h" +#elif defined( _WIN32 ) +#include <windows.h> +#elif POSIX +char *GetCommandLine(); +#endif +#include "resource.h" +#include "tier0/valve_on.h" +#include "tier0/threadtools.h" +#include "tier0/icommandline.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +class CDialogInitInfo +{ +public: + const tchar *m_pFilename; + int m_iLine; + const tchar *m_pExpression; +}; + + +class CAssertDisable +{ +public: + tchar m_Filename[512]; + + // If these are not -1, then this CAssertDisable only disables asserts on lines between + // these values (inclusive). + int m_LineMin; + int m_LineMax; + + // Decremented each time we hit this assert and ignore it, until it's 0. + // Then the CAssertDisable is removed. + // If this is -1, then we always ignore this assert. + int m_nIgnoreTimes; + + CAssertDisable *m_pNext; +}; + +#ifdef _WIN32 +static HINSTANCE g_hTier0Instance = 0; +#endif + +static bool g_bAssertsEnabled = true; + +static CAssertDisable *g_pAssertDisables = NULL; + +#if ( defined( _WIN32 ) && !defined( _X360 ) ) +static int g_iLastLineRange = 5; +static int g_nLastIgnoreNumTimes = 1; +#endif +#if defined( _X360 ) || defined( _PS3 ) + static int g_VXConsoleAssertReturnValue = -1; +#endif + +// Set to true if they want to break in the debugger. +static bool g_bBreak = false; + +static CDialogInitInfo g_Info; + +static bool g_bDisableAsserts = false; + + +// -------------------------------------------------------------------------------- // +// Internal functions. +// -------------------------------------------------------------------------------- // + +#if defined(_WIN32) && !defined(STATIC_TIER0) +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, // handle to the DLL module + DWORD fdwReason, // reason for calling function + LPVOID lpvReserved // reserved +) +{ + g_hTier0Instance = hinstDLL; + return true; +} +#endif + +static bool IsDebugBreakEnabled() +{ + static bool bResult = ( _tcsstr( Plat_GetCommandLine(), _T("-debugbreak") ) != NULL ); + return bResult; +} + +static bool AssertStack() +{ + static bool bResult = ( _tcsstr( Plat_GetCommandLine(), _T("-assertstack") ) != NULL ); + return bResult; +} + +static bool AreAssertsDisabled() +{ + static bool bResult = ( _tcsstr( Plat_GetCommandLine(), _T("-noassert") ) != NULL ); + return bResult || g_bDisableAsserts; +} + +static bool AllAssertOnce() +{ + static bool bResult = ( _tcsstr( Plat_GetCommandLine(), _T("-assertonce") ) != NULL ); + return bResult; +} + +static bool AreAssertsEnabledInFileLine( const tchar *pFilename, int iLine ) +{ + CAssertDisable **pPrev = &g_pAssertDisables; + CAssertDisable *pNext; + for ( CAssertDisable *pCur=g_pAssertDisables; pCur; pCur=pNext ) + { + pNext = pCur->m_pNext; + + if ( _tcsicmp( pFilename, pCur->m_Filename ) == 0 ) + { + // Are asserts disabled in the whole file? + bool bAssertsEnabled = true; + if ( pCur->m_LineMin == -1 && pCur->m_LineMax == -1 ) + bAssertsEnabled = false; + + // Are asserts disabled on the specified line? + if ( iLine >= pCur->m_LineMin && iLine <= pCur->m_LineMax ) + bAssertsEnabled = false; + + if ( !bAssertsEnabled ) + { + // If this assert is only disabled for the next N times, then countdown.. + if ( pCur->m_nIgnoreTimes > 0 ) + { + --pCur->m_nIgnoreTimes; + if ( pCur->m_nIgnoreTimes == 0 ) + { + // Remove this one from the list. + *pPrev = pNext; + delete pCur; + continue; + } + } + + return false; + } + } + + pPrev = &pCur->m_pNext; + } + + return true; +} + + +CAssertDisable* CreateNewAssertDisable( const tchar *pFilename ) +{ + CAssertDisable *pDisable = new CAssertDisable; + pDisable->m_pNext = g_pAssertDisables; + g_pAssertDisables = pDisable; + + pDisable->m_LineMin = pDisable->m_LineMax = -1; + pDisable->m_nIgnoreTimes = -1; + + _tcsncpy( pDisable->m_Filename, g_Info.m_pFilename, sizeof( pDisable->m_Filename ) - 1 ); + pDisable->m_Filename[ sizeof( pDisable->m_Filename ) - 1 ] = 0; + + return pDisable; +} + + +void IgnoreAssertsInCurrentFile() +{ + CreateNewAssertDisable( g_Info.m_pFilename ); +} + + +CAssertDisable* IgnoreAssertsNearby( int nRange ) +{ + CAssertDisable *pDisable = CreateNewAssertDisable( g_Info.m_pFilename ); + pDisable->m_LineMin = g_Info.m_iLine - nRange; + pDisable->m_LineMax = g_Info.m_iLine - nRange; + return pDisable; +} + + +#if ( defined( _WIN32 ) && !defined( _X360 ) ) +INT_PTR CALLBACK AssertDialogProc( + HWND hDlg, // handle to dialog box + UINT uMsg, // message + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter +) +{ + switch( uMsg ) + { + case WM_INITDIALOG: + { +#ifdef TCHAR_IS_WCHAR + SetDlgItemTextW( hDlg, IDC_ASSERT_MSG_CTRL, g_Info.m_pExpression ); + SetDlgItemTextW( hDlg, IDC_FILENAME_CONTROL, g_Info.m_pFilename ); +#else + SetDlgItemText( hDlg, IDC_ASSERT_MSG_CTRL, g_Info.m_pExpression ); + SetDlgItemText( hDlg, IDC_FILENAME_CONTROL, g_Info.m_pFilename ); +#endif + SetDlgItemInt( hDlg, IDC_LINE_CONTROL, g_Info.m_iLine, false ); + SetDlgItemInt( hDlg, IDC_IGNORE_NUMLINES, g_iLastLineRange, false ); + SetDlgItemInt( hDlg, IDC_IGNORE_NUMTIMES, g_nLastIgnoreNumTimes, false ); + + // Center the dialog. + RECT rcDlg, rcDesktop; + GetWindowRect( hDlg, &rcDlg ); + GetWindowRect( GetDesktopWindow(), &rcDesktop ); + SetWindowPos( + hDlg, + HWND_TOP, + ((rcDesktop.right-rcDesktop.left) - (rcDlg.right-rcDlg.left)) / 2, + ((rcDesktop.bottom-rcDesktop.top) - (rcDlg.bottom-rcDlg.top)) / 2, + 0, + 0, + SWP_NOSIZE ); + } + return true; + + case WM_COMMAND: + { + switch( LOWORD( wParam ) ) + { + case IDC_IGNORE_FILE: + { + IgnoreAssertsInCurrentFile(); + EndDialog( hDlg, 0 ); + return true; + } + + // Ignore this assert N times. + case IDC_IGNORE_THIS: + { + BOOL bTranslated = false; + UINT value = GetDlgItemInt( hDlg, IDC_IGNORE_NUMTIMES, &bTranslated, false ); + if ( bTranslated && value > 1 ) + { + CAssertDisable *pDisable = IgnoreAssertsNearby( 0 ); + pDisable->m_nIgnoreTimes = value - 1; + g_nLastIgnoreNumTimes = value; + } + + EndDialog( hDlg, 0 ); + return true; + } + + // Always ignore this assert. + case IDC_IGNORE_ALWAYS: + { + IgnoreAssertsNearby( 0 ); + EndDialog( hDlg, 0 ); + return true; + } + + case IDC_IGNORE_NEARBY: + { + BOOL bTranslated = false; + UINT value = GetDlgItemInt( hDlg, IDC_IGNORE_NUMLINES, &bTranslated, false ); + if ( !bTranslated || value < 1 ) + return true; + + IgnoreAssertsNearby( value ); + EndDialog( hDlg, 0 ); + return true; + } + + case IDC_IGNORE_ALL: + { + g_bAssertsEnabled = false; + EndDialog( hDlg, 0 ); + return true; + } + + case IDC_BREAK: + { + g_bBreak = true; + EndDialog( hDlg, 0 ); + return true; + } + } + + case WM_KEYDOWN: + { + // Escape? + if ( wParam == 2 ) + { + // Ignore this assert. + EndDialog( hDlg, 0 ); + return true; + } + } + + } + return true; + } + + return FALSE; +} + + +static HWND g_hBestParentWindow; + + +static BOOL CALLBACK ParentWindowEnumProc( + HWND hWnd, // handle to parent window + LPARAM lParam // application-defined value +) +{ + if ( IsWindowVisible( hWnd ) ) + { + DWORD procID; + GetWindowThreadProcessId( hWnd, &procID ); + if ( procID == (DWORD)lParam ) + { + g_hBestParentWindow = hWnd; + return FALSE; // don't iterate any more. + } + } + return TRUE; +} + + +static HWND FindLikelyParentWindow() +{ + // Enumerate top-level windows and take the first visible one with our processID. + g_hBestParentWindow = NULL; + EnumWindows( ParentWindowEnumProc, GetCurrentProcessId() ); + return g_hBestParentWindow; +} +#endif + +// -------------------------------------------------------------------------------- // +// Interface functions. +// -------------------------------------------------------------------------------- // + +// provides access to the global that turns asserts on and off +PLATFORM_INTERFACE bool AreAllAssertsDisabled() +{ + return !g_bAssertsEnabled; +} + +PLATFORM_INTERFACE void SetAllAssertsDisabled( bool bAssertsDisabled ) +{ + g_bAssertsEnabled = !bAssertsDisabled; +} + + +PLATFORM_INTERFACE bool ShouldUseNewAssertDialog() +{ + static bool bMPIWorker = ( _tcsstr( Plat_GetCommandLine(), _T("-mpi_worker") ) != NULL ); + if ( bMPIWorker ) + { + return false; + } + +#ifdef DBGFLAG_ASSERTDLG + return true; // always show an assert dialog +#else + return Plat_IsInDebugSession(); // only show an assert dialog if the process is being debugged +#endif // DBGFLAG_ASSERTDLG +} + + +PLATFORM_INTERFACE bool DoNewAssertDialog( const tchar *pFilename, int line, const tchar *pExpression ) +{ + LOCAL_THREAD_LOCK(); + + if ( AreAssertsDisabled() ) + return false; + + // If they have the old mode enabled (always break immediately), then just break right into + // the debugger like we used to do. + if ( IsDebugBreakEnabled() ) + return true; + + // Have ALL Asserts been disabled? + if ( !g_bAssertsEnabled ) + return false; + + // Has this specific Assert been disabled? + if ( !AreAssertsEnabledInFileLine( pFilename, line ) ) + return false; + + // Now create the dialog. + g_Info.m_pFilename = pFilename; + g_Info.m_iLine = line; + g_Info.m_pExpression = pExpression; + + if ( AssertStack() ) + { + IgnoreAssertsNearby( 0 ); + // @TODO: add-back callstack spew support + Warning( "%s (%d) : Assertion callstack...(NOT IMPLEMENTED IN NEW LOGGING SYSTEM.)\n", pFilename, line ); + // Warning_SpewCallStack( 10, "%s (%d) : Assertion callstack...\n", pFilename, line ); + return false; + } + + if( AllAssertOnce() ) + { + IgnoreAssertsNearby( 0 ); + } + + g_bBreak = false; + +#if defined( _X360 ) + + char cmdString[XBX_MAX_RCMDLENGTH]; + + // Before calling VXConsole, init the global variable that receives the result + g_VXConsoleAssertReturnValue = -1; + + // Message VXConsole to pop up a PC-side Assert dialog + _snprintf( cmdString, sizeof(cmdString), "Assert() 0x%.8x File: %s\tLine: %d\t%s", + &g_VXConsoleAssertReturnValue, pFilename, line, pExpression ); + XBX_SendRemoteCommand( cmdString, false ); + + // We sent a synchronous message, so g_xbx_dbgVXConsoleAssertReturnValue should have been overwritten by now + if ( g_VXConsoleAssertReturnValue == -1 ) + { + // VXConsole isn't connected/running - default to the old behaviour (break) + g_bBreak = true; + } + else + { + // Respond to what the user selected + switch( g_VXConsoleAssertReturnValue ) + { + case ASSERT_ACTION_IGNORE_FILE: + IgnoreAssertsInCurrentFile(); + break; + case ASSERT_ACTION_IGNORE_THIS: + // Ignore this Assert once + break; + case ASSERT_ACTION_BREAK: + // Break on this Assert + g_bBreak = true; + break; + case ASSERT_ACTION_IGNORE_ALL: + // Ignore all Asserts from now on + g_bAssertsEnabled = false; + break; + case ASSERT_ACTION_IGNORE_ALWAYS: + // Ignore this Assert from now on + IgnoreAssertsNearby( 0 ); + break; + case ASSERT_ACTION_OTHER: + default: + // Error... just break + XBX_Error( "DoNewAssertDialog: invalid Assert response returned from VXConsole - breaking to debugger" ); + g_bBreak = true; + break; + } + } +#elif defined( _PS3 ) + // There are a few ways to handle this sort of assert behavior with the PS3 / Target Manager API. + // One is to use a DebuggerBreak per usual, and then SNProcessContinue in the TMAPI to make + // the game resume after a breakpoint. (You can use snIsDebuggerPresent() to determine if + // the debugger is attached, although really it doesn't matter here.) + // This doesn't work because the DebuggerBreak() is actually an interrupt op, and so Continue() + // won't continue past it -- you need to do that from inside the ProDG debugger itself. + // Another is to wait on a mutex here and then trip it from the TMAPI, but there isn't + // a clean way to trip sync primitives from TMAPI. + // Another way is to suspend the thread here and have TMAPI resume it. + // The simplest way is to spin-wait on a shared variable that you expect the + // TMAPI to poke into memory. I'm trying that. + + char cmdString[XBX_MAX_RCMDLENGTH]; + + // Before calling VXConsole, init the global variable that receives the result + g_VXConsoleAssertReturnValue = -1; + + // Message VXConsole to pop up a PC-side Assert dialog + _snprintf( cmdString, sizeof(cmdString), "Assert() 0x%.8x File: %s\tLine: %d\t%s", + &g_VXConsoleAssertReturnValue, pFilename, line, pExpression ); + XBX_SendRemoteCommand( cmdString, false ); + + if ( g_pValvePS3Console->IsConsoleConnected() ) + { + // DebuggerBreak(); + + while ( g_VXConsoleAssertReturnValue == -1 ) + { + ThreadSleep( 1000 ); + } + + // assume that the VX has poked the return value + // Respond to what the user selected + switch( g_VXConsoleAssertReturnValue ) + { + case ASSERT_ACTION_IGNORE_FILE: + IgnoreAssertsInCurrentFile(); + break; + case ASSERT_ACTION_IGNORE_THIS: + // Ignore this Assert once + break; + case ASSERT_ACTION_BREAK: + // Break on this Assert + g_bBreak = true; + break; + case ASSERT_ACTION_IGNORE_ALL: + // Ignore all Asserts from now on + g_bAssertsEnabled = false; + break; + case ASSERT_ACTION_IGNORE_ALWAYS: + // Ignore this Assert from now on + IgnoreAssertsNearby( 0 ); + break; + case ASSERT_ACTION_OTHER: + default: + // nothing. + break; + } + } + else if ( g_pValvePS3Console->IsDebuggerPresent() ) + { + g_bBreak = true; + } + else + { + // ignore the assert + } + + +#elif defined( POSIX ) + + fprintf(stderr, "%s %i %s\n", pFilename, line, pExpression); + if ( getenv( "RAISE_ON_ASSERT" ) ) + { + DebuggerBreak(); + g_bBreak = true; + } + +#elif defined( _WIN32 ) + + if ( !g_hTier0Instance || !ThreadInMainThread() ) + { + int result = MessageBox( NULL, pExpression, "Assertion Failed", MB_SYSTEMMODAL | MB_CANCELTRYCONTINUE ); + + if ( result == IDCANCEL ) + { + IgnoreAssertsNearby( 0 ); + } + else if ( result == IDCONTINUE ) + { + g_bBreak = true; + } + } + else + { + HWND hParentWindow = FindLikelyParentWindow(); + + DialogBox( g_hTier0Instance, MAKEINTRESOURCE( IDD_ASSERT_DIALOG ), hParentWindow, AssertDialogProc ); + } + +#endif + + return g_bBreak; +} + diff --git a/external/vpc/tier0/commandline.cpp b/external/vpc/tier0/commandline.cpp new file mode 100644 index 0000000..2620b2e --- /dev/null +++ b/external/vpc/tier0/commandline.cpp @@ -0,0 +1,676 @@ +//===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//===========================================================================// + +#include "pch_tier0.h" +#include "tier0/icommandline.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "tier0/dbg.h" +#include "tier0_strtools.h" +#include "tier1/strtools.h" // this is included for the definition of V_isspace() + +#ifdef PLATFORM_POSIX +#include <limits.h> +#define _MAX_PATH PATH_MAX +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +static const int MAX_PARAMETER_LEN = 128; + +//----------------------------------------------------------------------------- +// Purpose: Implements ICommandLine +//----------------------------------------------------------------------------- +class CCommandLine : public ICommandLine +{ +public: + // Construction + CCommandLine( void ); + virtual ~CCommandLine( void ); + + // Implements ICommandLine + virtual void CreateCmdLine( const char *commandline ); + virtual void CreateCmdLine( int argc, char **argv ); + virtual const char *GetCmdLine( void ) const; + virtual const char *CheckParm( const char *psz, const char **ppszValue = 0 ) const; + + virtual void RemoveParm( const char *parm ); + virtual void AppendParm( const char *pszParm, const char *pszValues ); + + virtual int ParmCount() const; + virtual int FindParm( const char *psz ) const; + virtual const char* GetParm( int nIndex ) const; + + virtual const char *ParmValue( const char *psz, const char *pDefaultVal = NULL ) const; + virtual int ParmValue( const char *psz, int nDefaultVal ) const; + virtual float ParmValue( const char *psz, float flDefaultVal ) const; + virtual void SetParm( int nIndex, char const *pParm ); + +private: + enum + { + MAX_PARAMETER_LEN = 128, + MAX_PARAMETERS = 256, + }; + + // When the commandline contains @name, it reads the parameters from that file + void LoadParametersFromFile( const char *&pSrc, char *&pDst, intp maxDestLen, bool bInQuotes ); + + // Parse command line... + void ParseCommandLine(); + + // Frees the command line arguments + void CleanUpParms(); + + // Adds an argument.. + void AddArgument( const char *pFirst, const char *pLast ); + + // Copy of actual command line + char *m_pszCmdLine; + + // Pointers to each argument... + int m_nParmCount; + char *m_ppParms[MAX_PARAMETERS]; +}; + + +//----------------------------------------------------------------------------- +// Instance singleton and expose interface to rest of code +//----------------------------------------------------------------------------- +static CCommandLine g_CmdLine; +ICommandLine *CommandLine() +{ + return &g_CmdLine; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCommandLine::CCommandLine( void ) +{ + m_pszCmdLine = NULL; + m_nParmCount = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCommandLine::~CCommandLine( void ) +{ + CleanUpParms(); + delete[] m_pszCmdLine; +} + + +//----------------------------------------------------------------------------- +// Read commandline from file instead... +//----------------------------------------------------------------------------- +void CCommandLine::LoadParametersFromFile( const char *&pSrc, char *&pDst, intp maxDestLen, bool bInQuotes ) +{ + // Suck out the file name + char szFileName[ MAX_PATH ]; + char *pOut; + char *pDestStart = pDst; + + if ( maxDestLen < 3 ) + return; + + // Skip the @ sign + pSrc++; + + pOut = szFileName; + + char terminatingChar = ' '; + if ( bInQuotes ) + terminatingChar = '\"'; + + while ( *pSrc && *pSrc != terminatingChar ) + { + *pOut++ = *pSrc++; + if ( (pOut - szFileName) >= (MAX_PATH-1) ) + break; + } + + *pOut = '\0'; + + // Skip the space after the file name + if ( *pSrc ) + pSrc++; + + // Now read in parameters from file + FILE *fp = fopen( szFileName, "r" ); + if ( fp ) + { + char c; + c = (char)fgetc( fp ); + while ( c != EOF ) + { + // Turn return characters into spaces + if ( c == '\n' ) + c = ' '; + + *pDst++ = c; + + // Don't go past the end, and allow for our terminating space character AND a terminating null character. + if ( (pDst - pDestStart) >= (maxDestLen-2) ) + break; + + // Get the next character, if there are more + c = (char)fgetc( fp ); + } + + // Add a terminating space character + *pDst++ = ' '; + + fclose( fp ); + } + else + { + printf( "Parameter file '%s' not found, skipping...", szFileName ); + } +} + + +//----------------------------------------------------------------------------- +// Creates a command line from the arguments passed in +//----------------------------------------------------------------------------- +void CCommandLine::CreateCmdLine( int argc, char **argv ) +{ + char cmdline[2048]; + cmdline[0] = 0; + const int MAX_CHARS = sizeof(cmdline) - 1; + cmdline[MAX_CHARS] = 0; + for ( int i = 0; i < argc; ++i ) + { + strncat( cmdline, "\"", MAX_CHARS ); + strncat( cmdline, argv[i], MAX_CHARS ); + strncat( cmdline, "\"", MAX_CHARS ); + strncat( cmdline, " ", MAX_CHARS ); + } + + CreateCmdLine( cmdline ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Create a command line from the passed in string +// Note that if you pass in a @filename, then the routine will read settings +// from a file instead of the command line +//----------------------------------------------------------------------------- +void CCommandLine::CreateCmdLine( const char *commandline ) +{ + if ( m_pszCmdLine ) + { + delete[] m_pszCmdLine; + } + + char szFull[ 4096 ]; + + char *pDst = szFull; + const char *pSrc = commandline; + + bool bInQuotes = false; + const char *pInQuotesStart = 0; + while ( *pSrc ) + { + // Is this an unslashed quote? + if ( *pSrc == '"' ) + { + if ( pSrc == commandline || ( pSrc[-1] != '/' && pSrc[-1] != '\\' ) ) + { + bInQuotes = !bInQuotes; + pInQuotesStart = pSrc + 1; + } + } + + if ( *pSrc == '@' ) + { + if ( pSrc == commandline || (!bInQuotes && V_isspace( pSrc[-1] )) || (bInQuotes && pSrc == pInQuotesStart) ) + { + LoadParametersFromFile( pSrc, pDst, sizeof( szFull ) - (pDst - szFull), bInQuotes ); + if ( bInQuotes ) + { + // Back up over the opening quote which has already been copied to pDst. + // Otherwise we end up with an orphaned single quote which causes later + // parsing problems. + --pDst; + Assert( *pDst == '\"' ); + } + // The opening quote, if any, is now gone. + bInQuotes = false; + continue; + } + } + + // Don't go past the end. + if ( (pDst - szFull) >= (sizeof( szFull ) - 1) ) + break; + + *pDst++ = *pSrc++; + } + + *pDst = '\0'; + + size_t len = strlen( szFull ) + 1; + m_pszCmdLine = new char[len]; + memcpy( m_pszCmdLine, szFull, len ); + +#if defined( PLATFORM_PS3 ) + Plat_SetCommandLine( m_pszCmdLine ); +#endif + + ParseCommandLine(); +} + + +//----------------------------------------------------------------------------- +// Finds a string in another string with a case insensitive test +//----------------------------------------------------------------------------- +static char * _stristr( char * pStr, const char * pSearch ) +{ + AssertValidStringPtr(pStr); + AssertValidStringPtr(pSearch); + + if (!pStr || !pSearch) + return 0; + + char* pLetter = pStr; + + // Check the entire string + while (*pLetter != 0) + { + // Skip over non-matches + if (tolower((unsigned char)*pLetter) == tolower((unsigned char)*pSearch)) + { + // Check for match + char const* pMatch = pLetter + 1; + char const* pTest = pSearch + 1; + while (*pTest != 0) + { + // We've run off the end; don't bother. + if (*pMatch == 0) + return 0; + + if (tolower((unsigned char)*pMatch) != tolower((unsigned char)*pTest)) + break; + + ++pMatch; + ++pTest; + } + + // Found a match! + if (*pTest == 0) + return pLetter; + } + + ++pLetter; + } + + return 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Remove specified string ( and any args attached to it ) from command line +// Input : *pszParm - +//----------------------------------------------------------------------------- +void CCommandLine::RemoveParm( const char *pszParm ) +{ + if ( !m_pszCmdLine ) + return; + + // Search for first occurrence of pszParm + char *p, *found; + char *pnextparam; + intp n; + size_t curlen; + + p = m_pszCmdLine; + while ( *p ) + { + curlen = strlen( p ); + + found = _stristr( p, pszParm ); + if ( !found ) + break; + + pnextparam = found + 1; + bool bHadQuote = false; + if ( found > m_pszCmdLine && found[-1] == '\"' ) + bHadQuote = true; + + while ( pnextparam && *pnextparam && (*pnextparam != ' ') && (*pnextparam != '\"') ) + pnextparam++; + + if ( pnextparam && ( static_cast<size_t>( pnextparam - found ) > strlen( pszParm ) ) ) + { + p = pnextparam; + continue; + } + + while ( pnextparam && *pnextparam && (*pnextparam != '-') && (*pnextparam != '+') ) + pnextparam++; + + if ( bHadQuote ) + { + found--; + } + + if ( pnextparam && *pnextparam ) + { + // We are either at the end of the string, or at the next param. Just chop out the current param. + n = curlen - ( pnextparam - p ); // # of characters after this param. + memmove( found, pnextparam, n ); + + found[n] = '\0'; + } + else + { + // Clear out rest of string. + n = pnextparam - found; + memset( found, 0, n ); + } + } + + // Strip and trailing ' ' characters left over. + while ( 1 ) + { + intp len = strlen( m_pszCmdLine ); + if ( len == 0 || m_pszCmdLine[ len - 1 ] != ' ' ) + break; + + m_pszCmdLine[len - 1] = '\0'; + } + + ParseCommandLine(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Append parameter and argument values to command line +// Input : *pszParm - +// *pszValues - +//----------------------------------------------------------------------------- +void CCommandLine::AppendParm( const char *pszParm, const char *pszValues ) +{ + intp nNewLength = 0; + char *pCmdString; + + nNewLength = strlen( pszParm ); // Parameter. + if ( pszValues ) + nNewLength += strlen( pszValues ) + 1; // Values + leading space character. + nNewLength++; // Terminal 0; + + if ( !m_pszCmdLine ) + { + m_pszCmdLine = new char[ nNewLength ]; + strcpy( m_pszCmdLine, pszParm ); + if ( pszValues ) + { + strcat( m_pszCmdLine, " " ); + strcat( m_pszCmdLine, pszValues ); + } + + ParseCommandLine(); + return; + } + + // Remove any remnants from the current Cmd Line. + RemoveParm( pszParm ); + + nNewLength += strlen( m_pszCmdLine ) + 1 + 1; + + pCmdString = new char[ nNewLength ]; + memset( pCmdString, 0, nNewLength ); + + strcpy ( pCmdString, m_pszCmdLine ); // Copy old command line. + strcat ( pCmdString, " " ); // Put in a space + strcat ( pCmdString, pszParm ); + if ( pszValues ) + { + strcat( pCmdString, " " ); + strcat( pCmdString, pszValues ); + } + + // Kill off the old one + delete[] m_pszCmdLine; + + // Point at the new command line. + m_pszCmdLine = pCmdString; + + ParseCommandLine(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Return current command line +// Output : const char +//----------------------------------------------------------------------------- +const char *CCommandLine::GetCmdLine( void ) const +{ + return m_pszCmdLine; +} + + +//----------------------------------------------------------------------------- +// Purpose: Search for the parameter in the current commandline +// Input : *psz - +// **ppszValue - +// Output : char +//----------------------------------------------------------------------------- +const char *CCommandLine::CheckParm( const char *psz, const char **ppszValue ) const +{ + if ( ppszValue ) + *ppszValue = NULL; + + int i = FindParm( psz ); + if ( i == 0 ) + return NULL; + + if ( ppszValue ) + { + if ( (i+1) >= m_nParmCount ) + { + *ppszValue = NULL; + } + else + { + *ppszValue = m_ppParms[i+1]; + } + } + + return m_ppParms[i]; +} + + +//----------------------------------------------------------------------------- +// Adds an argument.. +//----------------------------------------------------------------------------- +void CCommandLine::AddArgument( const char *pFirst, const char *pLast ) +{ + if ( pLast == pFirst ) + return; + + if ( m_nParmCount >= MAX_PARAMETERS ) + Error( "CCommandLine::AddArgument: exceeded %d parameters", MAX_PARAMETERS ); + + size_t nLen = ( pLast - pFirst ) + 1; + m_ppParms[m_nParmCount] = new char[nLen]; + memcpy( m_ppParms[m_nParmCount], pFirst, nLen - 1 ); + m_ppParms[m_nParmCount][nLen - 1] = 0; + + ++m_nParmCount; +} + + +//----------------------------------------------------------------------------- +// Parse command line... +//----------------------------------------------------------------------------- +void CCommandLine::ParseCommandLine() +{ + CleanUpParms(); + if (!m_pszCmdLine) + return; + + const char *pChar = m_pszCmdLine; + while ( *pChar && V_isspace(*pChar) ) + { + ++pChar; + } + + bool bInQuotes = false; + const char *pFirstLetter = NULL; + for ( ; *pChar; ++pChar ) + { + if ( bInQuotes ) + { + if ( *pChar != '\"' ) + continue; + + AddArgument( pFirstLetter, pChar ); + pFirstLetter = NULL; + bInQuotes = false; + continue; + } + + // Haven't started a word yet... + if ( !pFirstLetter ) + { + if ( *pChar == '\"' ) + { + bInQuotes = true; + pFirstLetter = pChar + 1; + continue; + } + + if ( V_isspace( *pChar ) ) + continue; + + pFirstLetter = pChar; + continue; + } + + // Here, we're in the middle of a word. Look for the end of it. + if ( V_isspace( *pChar ) ) + { + AddArgument( pFirstLetter, pChar ); + pFirstLetter = NULL; + } + } + + if ( pFirstLetter ) + { + AddArgument( pFirstLetter, pChar ); + } +} + + +//----------------------------------------------------------------------------- +// Individual command line arguments +//----------------------------------------------------------------------------- +void CCommandLine::CleanUpParms() +{ + for ( int i = 0; i < m_nParmCount; ++i ) + { + delete [] m_ppParms[i]; + m_ppParms[i] = NULL; + } + m_nParmCount = 0; +} + + +//----------------------------------------------------------------------------- +// Returns individual command line arguments +//----------------------------------------------------------------------------- +int CCommandLine::ParmCount() const +{ + return m_nParmCount; +} + +int CCommandLine::FindParm( const char *psz ) const +{ + // Start at 1 so as to not search the exe name + for ( int i = 1; i < m_nParmCount; ++i ) + { + if ( !V_tier0_stricmp( psz, m_ppParms[i] ) ) + return i; + } + return 0; +} + +const char* CCommandLine::GetParm( int nIndex ) const +{ + Assert( (nIndex >= 0) && (nIndex < m_nParmCount) ); + if ( (nIndex < 0) || (nIndex >= m_nParmCount) ) + return ""; + return m_ppParms[nIndex]; +} +void CCommandLine::SetParm( int nIndex, char const *pParm ) +{ + if ( pParm ) + { + Assert( (nIndex >= 0) && (nIndex < m_nParmCount) ); + if ( (nIndex >= 0) && (nIndex < m_nParmCount) ) + { + if ( m_ppParms[nIndex] ) + delete[] m_ppParms[nIndex]; + m_ppParms[nIndex] = strdup( pParm ); + } + + } + +} + + +//----------------------------------------------------------------------------- +// Returns the argument after the one specified, or the default if not found +//----------------------------------------------------------------------------- +const char *CCommandLine::ParmValue( const char *psz, const char *pDefaultVal ) const +{ + int nIndex = FindParm( psz ); + if (( nIndex == 0 ) || (nIndex == m_nParmCount - 1)) + return pDefaultVal; + + // Probably another cmdline parameter instead of a valid arg if it starts with '+' or '-' + if ( m_ppParms[nIndex + 1][0] == '-' || m_ppParms[nIndex + 1][0] == '+' ) + return pDefaultVal; + + return m_ppParms[nIndex + 1]; +} + +int CCommandLine::ParmValue( const char *psz, int nDefaultVal ) const +{ + int nIndex = FindParm( psz ); + if (( nIndex == 0 ) || (nIndex == m_nParmCount - 1)) + return nDefaultVal; + + // Probably another cmdline parameter instead of a valid arg if it starts with '+' or '-' + if ( m_ppParms[nIndex + 1][0] == '-' || m_ppParms[nIndex + 1][0] == '+' ) + return nDefaultVal; + + return atoi( m_ppParms[nIndex + 1] ); +} + +float CCommandLine::ParmValue( const char *psz, float flDefaultVal ) const +{ + int nIndex = FindParm( psz ); + if (( nIndex == 0 ) || (nIndex == m_nParmCount - 1)) + return flDefaultVal; + + // Probably another cmdline parameter instead of a valid arg if it starts with '+' or '-' + if ( m_ppParms[nIndex + 1][0] == '-' || m_ppParms[nIndex + 1][0] == '+' ) + return flDefaultVal; + + return atof( m_ppParms[nIndex + 1] ); +} diff --git a/external/vpc/tier0/cpu.cpp b/external/vpc/tier0/cpu.cpp new file mode 100644 index 0000000..420e1a1 --- /dev/null +++ b/external/vpc/tier0/cpu.cpp @@ -0,0 +1,697 @@ +//===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "pch_tier0.h" + +#if defined(_WIN32) && !defined(_X360) +#define WINDOWS_LEAN_AND_MEAN +#include <windows.h> +#include "cputopology.h" +#elif defined( PLATFORM_OSX ) +#include <sys/sysctl.h> +#endif + +#ifndef _PS3 +#include "tier0_strtools.h" +#endif + +//#include "tier1/strtools.h" // this is included for the definition of V_isspace() +#ifdef PLATFORM_WINDOWS_PC +#include <intrin.h> +#endif + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +const tchar* GetProcessorVendorId(); + +static bool cpuid(uint32 function, uint32& out_eax, uint32& out_ebx, uint32& out_ecx, uint32& out_edx) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#elif defined(GNUC) + asm("mov %%ebx, %%esi\n\t" + "cpuid\n\t" + "xchg %%esi, %%ebx" + : "=a" (out_eax), + "=S" (out_ebx), + "=c" (out_ecx), + "=d" (out_edx) + : "a" (function) + ); + return true; +#elif defined(_WIN64) + int pCPUInfo[4]; + __cpuid( pCPUInfo, (int)function ); + out_eax = pCPUInfo[0]; + out_ebx = pCPUInfo[1]; + out_ecx = pCPUInfo[2]; + out_edx = pCPUInfo[3]; + return false; +#else + bool retval = true; + uint32 local_eax, local_ebx, local_ecx, local_edx; + _asm pushad; + + __try + { + _asm + { + xor edx, edx // Clue the compiler that EDX & others is about to be used. + xor ecx, ecx + xor ebx, ebx // <Sergiy> Note: if I don't zero these out, cpuid sometimes won't work, I didn't find out why yet + mov eax, function // set up CPUID to return processor version and features + // 0 = vendor string, 1 = version info, 2 = cache info + cpuid // code bytes = 0fh, 0a2h + mov local_eax, eax // features returned in eax + mov local_ebx, ebx // features returned in ebx + mov local_ecx, ecx // features returned in ecx + mov local_edx, edx // features returned in edx + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + retval = false; + } + + out_eax = local_eax; + out_ebx = local_ebx; + out_ecx = local_ecx; + out_edx = local_edx; + + _asm popad + + return retval; +#endif +} + +static bool CheckMMXTechnology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return true; +#else + uint32 eax,ebx,edx,unused; + if ( !cpuid(1,eax,ebx,unused,edx) ) + return false; + + return ( edx & 0x800000 ) != 0; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: This is a bit of a hack because it appears +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +static bool IsWin98OrOlder() +{ +#if defined( _X360 ) || defined( _PS3 ) || defined( POSIX ) + return false; +#else + bool retval = false; + + OSVERSIONINFOEX osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + + BOOL bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi); + if( !bOsVersionInfoEx ) + { + // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO. + + osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + if ( !GetVersionEx ( (OSVERSIONINFO *) &osvi) ) + { + Error( _T("IsWin98OrOlder: Unable to get OS version information") ); + } + } + + switch (osvi.dwPlatformId) + { + case VER_PLATFORM_WIN32_NT: + // NT, XP, Win2K, etc. all OK for SSE + break; + case VER_PLATFORM_WIN32_WINDOWS: + // Win95, 98, Me can't do SSE + retval = true; + break; + case VER_PLATFORM_WIN32s: + // Can't really run this way I don't think... + retval = true; + break; + default: + break; + } + + return retval; +#endif +} + + +static bool CheckSSETechnology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return true; +#else + if ( IsWin98OrOlder() ) + { + return false; + } + + uint32 eax,ebx,edx,unused; + if ( !cpuid(1,eax,ebx,unused,edx) ) + { + return false; + } + + return ( edx & 0x2000000L ) != 0; +#endif +} + +static bool CheckSSE2Technology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + uint32 eax,ebx,edx,unused; + if ( !cpuid(1,eax,ebx,unused,edx) ) + return false; + + return ( edx & 0x04000000 ) != 0; +#endif +} + +bool CheckSSE3Technology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + uint32 eax,ebx,edx,ecx; + if( !cpuid(1,eax,ebx,ecx,edx) ) + return false; + + return ( ecx & 0x00000001 ) != 0; // bit 1 of ECX +#endif +} + +bool CheckSSSE3Technology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + // SSSE 3 is implemented by both Intel and AMD + // detection is done the same way for both vendors + uint32 eax,ebx,edx,ecx; + if( !cpuid(1,eax,ebx,ecx,edx) ) + return false; + + return ( ecx & ( 1 << 9 ) ) != 0; // bit 9 of ECX +#endif +} + +bool CheckSSE41Technology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + // SSE 4.1 is implemented by both Intel and AMD + // detection is done the same way for both vendors + + uint32 eax,ebx,edx,ecx; + if( !cpuid(1,eax,ebx,ecx,edx) ) + return false; + + return ( ecx & ( 1 << 19 ) ) != 0; // bit 19 of ECX +#endif +} + +bool CheckSSE42Technology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + // SSE4.2 is an Intel-only feature + + const char *pchVendor = GetProcessorVendorId(); + if ( 0 != V_tier0_stricmp( pchVendor, "GenuineIntel" ) ) + return false; + + uint32 eax,ebx,edx,ecx; + if( !cpuid(1,eax,ebx,ecx,edx) ) + return false; + + return ( ecx & ( 1 << 20 ) ) != 0; // bit 20 of ECX +#endif +} + + +bool CheckSSE4aTechnology( void ) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + // SSE 4a is an AMD-only feature + + const char *pchVendor = GetProcessorVendorId(); + if ( 0 != V_tier0_stricmp( pchVendor, "AuthenticAMD" ) ) + return false; + + uint32 eax,ebx,edx,ecx; + if( !cpuid( 0x80000001,eax,ebx,ecx,edx) ) + return false; + + return ( ecx & ( 1 << 6 ) ) != 0; // bit 6 of ECX +#endif +} + + +static bool Check3DNowTechnology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + uint32 eax, unused; + if ( !cpuid(0x80000000,eax,unused,unused,unused) ) + return false; + + if ( eax > 0x80000000L ) + { + if ( !cpuid(0x80000001,unused,unused,unused,eax) ) + return false; + + return ( eax & 1<<31 ) != 0; + } + return false; +#endif +} + +static bool CheckCMOVTechnology() +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + uint32 eax,ebx,edx,unused; + if ( !cpuid(1,eax,ebx,unused,edx) ) + return false; + + return ( edx & (1<<15) ) != 0; +#endif +} + +static bool CheckFCMOVTechnology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + uint32 eax,ebx,edx,unused; + if ( !cpuid(1,eax,ebx,unused,edx) ) + return false; + + return ( edx & (1<<16) ) != 0; +#endif +} + +static bool CheckRDTSCTechnology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + uint32 eax,ebx,edx,unused; + if ( !cpuid(1,eax,ebx,unused,edx) ) + return false; + + return ( edx & 0x10 ) != 0; +#endif +} + +// Return the Processor's vendor identification string, or "Generic_x86" if it doesn't exist on this CPU +const tchar* GetProcessorVendorId() +{ +#if defined( _X360 ) || defined( _PS3 ) + return "PPC"; +#else + uint32 unused, VendorIDRegisters[3]; + + static tchar VendorID[13]; + + memset( VendorID, 0, sizeof(VendorID) ); + if ( !cpuid(0,unused, VendorIDRegisters[0], VendorIDRegisters[2], VendorIDRegisters[1] ) ) + { + if ( IsPC() ) + { + _tcscpy( VendorID, _T( "Generic_x86" ) ); + } + else if ( IsX360() ) + { + _tcscpy( VendorID, _T( "PowerPC" ) ); + } + } + else + { + memcpy( VendorID+0, &(VendorIDRegisters[0]), sizeof( VendorIDRegisters[0] ) ); + memcpy( VendorID+4, &(VendorIDRegisters[1]), sizeof( VendorIDRegisters[1] ) ); + memcpy( VendorID+8, &(VendorIDRegisters[2]), sizeof( VendorIDRegisters[2] ) ); + } + + return VendorID; +#endif +} + +// Returns non-zero if Hyper-Threading Technology is supported on the processors and zero if not. +// If it's supported, it does not mean that it's been enabled. So we test another flag to see if it's enabled +// See Intel Processor Identification and the CPUID instruction Application Note 485 +// http://www.intel.com/Assets/PDF/appnote/241618.pdf +static bool HTSupported(void) +{ +#if ( defined( _X360 ) || defined( _PS3 ) ) + // not entirtely sure about the semantic of HT support, it being an intel name + // are we asking about HW threads or HT? + return true; +#else + enum { + HT_BIT = 0x10000000, // EDX[28] - Bit 28 set indicates Hyper-Threading Technology is supported in hardware. + FAMILY_ID = 0x0f00, // EAX[11:8] - Bit 11 thru 8 contains family processor id + EXT_FAMILY_ID = 0x0f00000, // EAX[23:20] - Bit 23 thru 20 contains extended family processor id + FAMILY_ID_386 = 0x0300, + FAMILY_ID_486 = 0x0400, // EAX[8:12] - 486, 487 and overdrive + FAMILY_ID_PENTIUM = 0x0500, // Pentium, Pentium OverDrive 60 - 200 + FAMILY_ID_PENTIUM_PRO = 0x0600,// P Pro, P II, P III, P M, Celeron M, Core Duo, Core Solo, Core2 Duo, Core2 Extreme, P D, Xeon model F, + // also 45-nm : Intel Atom, Core i7, Xeon MP ; see Intel Processor Identification and the CPUID instruction pg 20,21 + + FAMILY_ID_EXTENDED = 0x0F00 // P IV, Xeon, Celeron D, P D, + }; + + uint32 unused, + reg_eax = 0, + reg_ebx = 0, + reg_edx = 0, + vendor_id[3] = {0, 0, 0}; + + // verify cpuid instruction is supported + if( !cpuid(0,unused, vendor_id[0],vendor_id[2],vendor_id[1]) + || !cpuid(1,reg_eax,reg_ebx,unused,reg_edx) ) + return false; + + // <Sergiy> Previously, we detected P4 specifically; now, we detect GenuineIntel with HT enabled in general + // if (((reg_eax & FAMILY_ID) == FAMILY_ID_EXTENDED) || (reg_eax & EXT_FAMILY_ID)) + + // Check to see if this is an Intel Processor with HT or CMT capability , and if HT/CMT is enabled + if (vendor_id[0] == 'uneG' && vendor_id[1] == 'Ieni' && vendor_id[2] == 'letn') + return (reg_edx & HT_BIT) != 0 && // Genuine Intel Processor with Hyper-Threading Technology implemented + ((reg_ebx >> 16) & 0xFF) > 1 ; // Hyper-Threading OR Core Multi-Processing has been enabled + + return false; // This is not a genuine Intel processor. +#endif +} + +// See Intel Processor Identification and the CPUID instruction Application Note 485 +// http://www.intel.com/Assets/PDF/appnote/241618.pdf +int LogicalProcessorsPerCore() +{ +#if defined( _X360 ) || defined( _PS3 ) || defined( LINUX ) + return 2; // +#elif defined(_WIN32) + uint32 nMaxStandardFnSupported, nVendorId[3]; + if( !cpuid( 0, nMaxStandardFnSupported,nVendorId[0],nVendorId[2],nVendorId[1] ) ) + { + return 1; + } + + uint32 nFn1_Eax, nFn1_Ebx, nFn1_Ecx, nFn1_Edx; + if( !cpuid( 1, nFn1_Eax, nFn1_Ebx, nFn1_Ecx, nFn1_Edx) ) + { + return 1; + } + + enum CpuidFnMasks + { + HTT = 0x10000000, // Fn0000_0001 EDX[28] + LogicalProcessorCount = 0x00FF0000, // Fn0000_0001 EBX[23:16] + ApicId = 0xFF000000, // Fn0000_0001 EBX[31:24] + NC_Intel = 0xFC000000, // Fn0000_0004 EAX[31:26] + NC_Amd = 0x000000FF, // Fn8000_0008 ECX[7:0] + CmpLegacy_Amd = 0x00000002, // Fn8000_0001 ECX[1] + ApicIdCoreIdSize_Amd = 0x0000F000 // Fn8000_0008 ECX[15:12] + }; + + // Determine if hardware threading is enabled. + if( nFn1_Edx & HTT ) + { + // Determine the total number of logical processors per package. + int nLogProcsPerPkg = ( nFn1_Ebx & LogicalProcessorCount ) >> 16; + int nCoresPerPkg = 1; + + if( ( ( nFn1_Ebx >> 16 ) & 0xFF ) <= 1 ) // Has Hyper-Threading OR Core Multi-Processing not been enabled ? + { + // NOTE: This is only tested on Intel CPUs; I don't know if it's true on AMD, as I have no HT AMD to test on + return 1; // HT was turned off, for all intents and purposes in our engine it means one logical CPU per core + } + + // Determine the total number of cores per package. This info + // is extracted differently dependending on the cpu vendor. + if( nVendorId[0] == 'uneG' && nVendorId[1] == 'Ieni' && nVendorId[2] == 'letn' ) // GenuineIntel + { + if( nMaxStandardFnSupported >= 4 ) + { + uint32 nFn4_Eax, nFn4_Ebx, nFn4_Ecx, nFn4_Edx ; + if( cpuid( 4, nFn4_Eax, nFn4_Ebx, nFn4_Ecx, nFn4_Edx ) ) + { + nCoresPerPkg = ( ( nFn4_Eax & NC_Intel ) >> 26 ) + 1; + + } + } + // <Sergiy> as the DirectX CoreDetection sample goes, the logic is that on old processors where + // the functions aren't supported, we assume one core per package, multiple logical processors per package + // I suspect this may be wrong, especially for AMD processors. + return nLogProcsPerPkg / nCoresPerPkg; + } +#if 0 // <Sergiy> To make as concervative change as possible now, I'll skip AMD hyperthread detection + else + { + if( nVendorId[0] == 'htuA' && nVendorId[1] == 'itne' && nVendorId[2] == 'DMAc' ) // AuthenticAMD + { + uint32 nFnx8_Eax, nFnx8_Ebx, nFnx8_Ecx, nFnx8_Edx ; + if( cpuid( 0x80000008, nFnx8_Eax, nFnx8_Ebx, nFnx8_Ecx, nFnx8_Edx ) ) + { + // AMD reports the msb width of the CORE_ID bit field of the APIC ID + // in ApicIdCoreIdSize_Amd. The maximum value represented by the msb + // width is the theoretical number of cores the processor can support + // and not the actual number of current cores, which is how the msb width + // of the CORE_ID bit field has been traditionally determined. If the + // ApicIdCoreIdSize_Amd value is zero, then you use the traditional method + // to determine the CORE_ID msb width. + DWORD msbWidth = nFnx8_Ecx & ApicIdCoreIdSize_Amd; + if( msbWidth ) + { + // Set nCoresPerPkg to the maximum theortical number of cores + // the processor package can support (2 ^ width) so the APIC + // extractor object can be configured to extract the proper + // values from an APIC. + nCoresPerPkg = 1 << ( msbWidth >> 12 ); + } + else + { + // Set nCoresPerPkg to the actual number of cores being reported + // by the CPUID instruction. + nCoresPerPkg = ( nFnx8_Ecx & NC_Amd ) + 1; + } + } + } + // <Sergiy> as the DirectX CoreDetection sample goes, the logic is that on old processors where + // the functions aren't supported, we assume one core per package, multiple logical processors per package + // I suspect this may be wrong, especially for AMD processors. + return nLogProcsPerPkg / nCoresPerPkg; + } +#endif + } + return 1; +#endif +} + + + +// Measure the processor clock speed by sampling the cycle count, waiting +// for some fraction of a second, then measuring the elapsed number of cycles. +static int64 CalculateClockSpeed() +{ +#if defined( _X360 ) || defined(_PS3) + // Xbox360 and PS3 have the same clock speed and share a lot of characteristics on PPU + return 3200000000LL; +#else +#if defined( _WIN32 ) + LARGE_INTEGER waitTime, startCount, curCount; + CCycleCount start, end; + + // Take 1/32 of a second for the measurement. + QueryPerformanceFrequency( &waitTime ); + int scale = 5; + waitTime.QuadPart >>= scale; + + QueryPerformanceCounter( &startCount ); + start.Sample(); + do + { + QueryPerformanceCounter( &curCount ); + } + while ( curCount.QuadPart - startCount.QuadPart < waitTime.QuadPart ); + end.Sample(); + + return (end.m_Int64 - start.m_Int64) << scale; +#elif defined(POSIX) + uint64 CalculateCPUFreq(); // from cpu_linux.cpp + int64 freq =(int64)CalculateCPUFreq(); + if ( freq == 0 ) // couldn't calculate clock speed + { + Error( "Unable to determine CPU Frequency\n" ); + } + return freq; +#else + #error "Please implement Clock Speed function for this platform" +#endif +#endif +} + +static CPUInformation s_cpuInformation; + +const CPUInformation& GetCPUInformation() +{ + CPUInformation &pi = s_cpuInformation; + // Has the structure already been initialized and filled out? + if ( pi.m_Size == sizeof(pi) ) + return pi; + + // Redundant, but just in case the user somehow messes with the size. + memset(&pi, 0x0, sizeof(pi)); + + // Fill out the structure, and return it: + pi.m_Size = sizeof(pi); + + // Grab the processor frequency: + pi.m_Speed = CalculateClockSpeed(); + + // Get the logical and physical processor counts: + +#if defined( _X360 ) + pi.m_nPhysicalProcessors = 3; + pi.m_nLogicalProcessors = 6; +#elif defined( _PS3 ) + pi.m_nPhysicalProcessors = 1; + pi.m_nLogicalProcessors = 2; +#elif defined(_WIN32) && !defined( _X360 ) + SYSTEM_INFO si; + ZeroMemory( &si, sizeof(si) ); + + GetSystemInfo( &si ); + + // Sergiy: fixing: si.dwNumberOfProcessors is the number of logical processors according to experiments on i7, P4 and a DirectX sample (Aug'09) + // this is contrary to MSDN documentation on GetSystemInfo() + // + pi.m_nLogicalProcessors = si.dwNumberOfProcessors; + if ( 0 == V_tier0_stricmp( GetProcessorVendorId(), "AuthenticAMD" ) ) + { + // quick fix for AMD Phenom: it reports 3 logical cores and 4 physical cores; + // no AMD CPUs by the end of 2009 have HT, so we'll override HT detection here + pi.m_nPhysicalProcessors = pi.m_nLogicalProcessors; + } + else + { + CpuTopology topo; + pi.m_nPhysicalProcessors = topo.NumberOfSystemCores(); + } + + // Make sure I always report at least one, when running WinXP with the /ONECPU switch, + // it likes to report 0 processors for some reason. + if ( pi.m_nPhysicalProcessors == 0 && pi.m_nLogicalProcessors == 0 ) + { + Assert( !"Sergiy: apparently I didn't fix some CPU detection code completely. Let me know and I'll do my best to fix it soon." ); + pi.m_nPhysicalProcessors = 1; + pi.m_nLogicalProcessors = 1; + } +#elif defined(LINUX) + pi.m_nLogicalProcessors = 0; + pi.m_nPhysicalProcessors = 0; + const int k_cMaxProcessors = 256; + bool rgbProcessors[k_cMaxProcessors]; + memset( rgbProcessors, 0, sizeof( rgbProcessors ) ); + int cMaxCoreId = 0; + + FILE *fpCpuInfo = fopen( "/proc/cpuinfo", "r" ); + if ( fpCpuInfo ) + { + char rgchLine[256]; + while ( fgets( rgchLine, sizeof( rgchLine ), fpCpuInfo ) ) + { + if ( !strncasecmp( rgchLine, "processor", strlen( "processor" ) ) ) + { + pi.m_nLogicalProcessors++; + } + if ( !strncasecmp( rgchLine, "core id", strlen( "core id" ) ) ) + { + char *pchValue = strchr( rgchLine, ':' ); + cMaxCoreId = MAX( cMaxCoreId, atoi( pchValue + 1 ) ); + } + if ( !strncasecmp( rgchLine, "physical id", strlen( "physical id" ) ) ) + { + // it seems (based on survey data) that we can see + // processor N (N > 0) when it's the only processor in + // the system. so keep track of each processor + char *pchValue = strchr( rgchLine, ':' ); + int cPhysicalId = atoi( pchValue + 1 ); + if ( cPhysicalId < k_cMaxProcessors ) + rgbProcessors[cPhysicalId] = true; + } + /* this code will tell us how many physical chips are in the machine, but we want + core count, so for the moment, each processor counts as both logical and physical. + if ( !strncasecmp( rgchLine, "physical id ", strlen( "physical id " ) ) ) + { + char *pchValue = strchr( rgchLine, ':' ); + pi.m_nPhysicalProcessors = MAX( pi.m_nPhysicalProcessors, atol( pchValue ) ); + } + */ + } + fclose( fpCpuInfo ); + for ( int i = 0; i < k_cMaxProcessors; i++ ) + if ( rgbProcessors[i] ) + pi.m_nPhysicalProcessors++; + pi.m_nPhysicalProcessors *= ( cMaxCoreId + 1 ); + } + else + { + pi.m_nLogicalProcessors = 1; + pi.m_nPhysicalProcessors = 1; + Assert( !"couldn't read cpu information from /proc/cpuinfo" ); + } + +#elif defined(OSX) + int mib[2], num_cpu = 1; + size_t len; + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + len = sizeof(num_cpu); + sysctl(mib, 2, &num_cpu, &len, NULL, 0); + pi.m_nPhysicalProcessors = num_cpu; + pi.m_nLogicalProcessors = num_cpu; + +#endif + + // Determine Processor Features: + pi.m_bRDTSC = CheckRDTSCTechnology(); + pi.m_bCMOV = CheckCMOVTechnology(); + pi.m_bFCMOV = CheckFCMOVTechnology(); + pi.m_bMMX = CheckMMXTechnology(); + pi.m_bSSE = CheckSSETechnology(); + pi.m_bSSE2 = CheckSSE2Technology(); + pi.m_bSSE3 = CheckSSE3Technology(); + pi.m_bSSSE3 = CheckSSSE3Technology(); + pi.m_bSSE4a = CheckSSE4aTechnology(); + pi.m_bSSE41 = CheckSSE41Technology(); + pi.m_bSSE42 = CheckSSE42Technology(); + pi.m_b3DNow = Check3DNowTechnology(); + pi.m_szProcessorID = (tchar*)GetProcessorVendorId(); + pi.m_bHT = pi.m_nPhysicalProcessors < pi.m_nLogicalProcessors; //HTSupported(); + + return pi; +} + diff --git a/external/vpc/tier0/cpu_posix.cpp b/external/vpc/tier0/cpu_posix.cpp new file mode 100644 index 0000000..4fe2db8 --- /dev/null +++ b/external/vpc/tier0/cpu_posix.cpp @@ -0,0 +1,143 @@ +//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: determine CPU speed under linux +// +// $NoKeywords: $ +//=============================================================================// +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/sysctl.h> +#include <sys/time.h> +#include <unistd.h> +#include <tier0/platform.h> +#include <errno.h> + +#define rdtsc(x) \ + __asm__ __volatile__ ("rdtsc" : "=A" (x)) + +class TimeVal +{ +public: + TimeVal() {} + TimeVal& operator=(const TimeVal &val) { m_TimeVal = val.m_TimeVal; } + inline double operator-(const TimeVal &left) + { + uint64 left_us = (uint64) left.m_TimeVal.tv_sec * 1000000 + left.m_TimeVal.tv_usec; + uint64 right_us = (uint64) m_TimeVal.tv_sec * 1000000 + m_TimeVal.tv_usec; + uint64 diff_us = left_us - right_us; + return diff_us/1000000; + } + + timeval m_TimeVal; +}; + +// Compute the positive difference between two 64 bit numbers. +static inline uint64 diff(uint64 v1, uint64 v2) +{ + uint64 d = v1 - v2; + if (d >= 0) return d; else return -d; +} + +#ifdef OSX +uint64 GetCPUFreqFromPROC() +{ + int mib[2] = {CTL_HW, HW_CPU_FREQ}; + uint64 frequency = 0; + size_t len = sizeof(frequency); + + if (sysctl(mib, 2, &frequency, &len, NULL, 0) == -1) + return 0; + return frequency; +} +#else +uint64 GetCPUFreqFromPROC() +{ + double mhz = 0; + char line[1024], *s, search_str[] = "cpu MHz"; + FILE *fp; + + /* open proc/cpuinfo */ + if ((fp = fopen("/proc/cpuinfo", "r")) == NULL) + { + return 0; + } + + /* ignore all lines until we reach MHz information */ + while (fgets(line, 1024, fp) != NULL) + { + if (strstr(line, search_str) != NULL) + { + /* ignore all characters in line up to : */ + for (s = line; *s && (*s != ':'); ++s); + /* get MHz number */ + if (*s && (sscanf(s+1, "%lf", &mhz) == 1)) + break; + } + } + + if (fp!=NULL) fclose(fp); + + return (uint64)(mhz*1000000); +} +#endif + + +uint64 CalculateCPUFreq() +{ +#ifdef LINUX + char const *pFreq = getenv("CPU_MHZ"); + if ( pFreq ) + { + uint64 retVal = 1000000; + return retVal * atoi( pFreq ); + } +#endif + + // Compute the period. Loop until we get 3 consecutive periods that + // are the same to within a small error. The error is chosen + // to be +/- 0.02% on a P-200. + const uint64 error = 40000; + const int max_iterations = 600; + int count; + uint64 period, period1 = error * 2, period2 = 0, period3 = 0; + + for (count = 0; count < max_iterations; count++) + { + TimeVal start_time, end_time; + uint64 start_tsc, end_tsc; + gettimeofday (&start_time.m_TimeVal, 0); + rdtsc (start_tsc); + usleep (5000); // sleep for 5 msec + gettimeofday (&end_time.m_TimeVal, 0); + rdtsc (end_tsc); + + period3 = (end_tsc - start_tsc) / (end_time - start_time); + + if (diff (period1, period2) <= error && + diff (period2, period3) <= error && + diff (period1, period3) <= error) + break; + + period1 = period2; + period2 = period3; + } + + if (count == max_iterations) + { + return GetCPUFreqFromPROC(); // fall back to /proc + } + + // Set the period to the average period measured. + period = (period1 + period2 + period3) / 3; + + // Some Pentiums have broken TSCs that increment very + // slowly or unevenly. + if (period < 10000000) + { + return GetCPUFreqFromPROC(); // fall back to /proc + } + + return period; +} + diff --git a/external/vpc/tier0/cputopology.cpp b/external/vpc/tier0/cputopology.cpp new file mode 100644 index 0000000..1125cae --- /dev/null +++ b/external/vpc/tier0/cputopology.cpp @@ -0,0 +1,1018 @@ +//------------------------------------------------------------------------------------- +// CpuTopology.cpp +// +// CpuToplogy class implementation. +// +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------------- +#include "pch_tier0.h" + +#if defined(_WIN32) && !defined(_X360) && !defined( _PS3 ) +#include "cputopology.h" +#include <stdlib.h> +#include <crtdbg.h> + +#undef malloc +#undef free +#ifdef _WIN64 + // Inline assembly is not supported in 64-bit. Ideally, we would prefer to + // use the __cpuid intrinsic to avoid having to drop to assembly altogether. + // However, as of MSVC 9.0, the __cpuid intrinsic does not enable the + // ability to set the ECX register, which is required for obtaining certain + // extended CPU information. To overcome these issues, we must call the + // Cpuid64() external function, which is written in assembly and located in + // the cpuid64.asm file included in this project. This project contains a build + // step that invokes 64-bit MASM on cpuid64.asm when building a 64-bit target. + extern "C" void Cpuid64(void* argsPtr); +#endif + +//--------------------------------------------------------------------------------- +// Name: ICpuToplogy +// Desc: Specifies the interface that each class that provides an implementation +// for extracting cpu topology must conform to. This is the Implementor +// class in the traditional Bridge Pattern. +//--------------------------------------------------------------------------------- +class ICpuTopology +{ +public: + virtual ~ICpuTopology() + { + } + virtual BOOL IsDefaultImpl() const = 0; + virtual DWORD NumberOfProcessCores() const = 0; + virtual DWORD NumberOfSystemCores() const = 0; + virtual DWORD_PTR CoreAffinityMask( DWORD coreIdx ) const = 0; +}; + + +namespace +{ +/////////////////////////////////////////////////////////////////////////////////// +// Local Class Definitions +/////////////////////////////////////////////////////////////////////////////////// + +//--------------------------------------------------------------------------------- +// Name: DefaultImpl +// Desc: Provides a default implementation for the ICpuTopology interface when +// GetLogicalProcessorInformation and CPUID are not supported for whatever +// reason. This is a ConcreteImplementor class in the traditional Bridge +// Pattern. +//--------------------------------------------------------------------------------- +class DefaultImpl : public ICpuTopology +{ +public: + //----------------------------------------------------------------------------- + // DefaultImpl::IsDefaultImpl + //----------------------------------------------------------------------------- + /*virtual*/ BOOL IsDefaultImpl() const + { + return TRUE; + } + + //----------------------------------------------------------------------------- + // DefaultImpl::NumberOfProcessCores + //----------------------------------------------------------------------------- + /*virtual*/ DWORD NumberOfProcessCores() const + { + return 1; + } + + //----------------------------------------------------------------------------- + // DefaultImpl::IsNumberOfSystemCores + //----------------------------------------------------------------------------- + /*virtual*/ DWORD NumberOfSystemCores() const + { + return 1; + } + + //----------------------------------------------------------------------------- + // DefaultImpl::CoreAffinityMask + //----------------------------------------------------------------------------- + /*virtual*/ DWORD_PTR CoreAffinityMask( DWORD coreIdx ) const + { + DWORD_PTR coreAffinity = 0; + if( 1 == coreIdx ) + { + DWORD_PTR dwSystemAffinity; + GetProcessAffinityMask( GetCurrentProcess(), &coreAffinity, &dwSystemAffinity ); + } + return coreAffinity; + } +}; + +//--------------------------------------------------------------------------------- +// Name: GlpiImpl +// Desc: Provides the GetLogicalProcessorInformation implementation for the +// ICpuTopology interface. This is a ConcreteImplementor class in the +// traditional Bridge Pattern. +//--------------------------------------------------------------------------------- +class GlpiImpl : public ICpuTopology +{ +public: + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::GlpiImpl + // Desc: Initializes the internal structures/data with information retrieved + // from a call to GetLogicalProcessorInformation. + //----------------------------------------------------------------------------- + GlpiImpl() : m_pSlpi( NULL ), + m_nItems( 0 ) + { + _ASSERT( IsSupported() ); + + GlpiFnPtr pGlpi = GetGlpiFn_(); + _ASSERT( pGlpi ); + + DWORD cbBuffer = 0; + pGlpi( 0, &cbBuffer ); + + m_pSlpi = ( SYSTEM_LOGICAL_PROCESSOR_INFORMATION* )malloc( cbBuffer ); + pGlpi( m_pSlpi, &cbBuffer ); + m_nItems = cbBuffer / sizeof( SYSTEM_LOGICAL_PROCESSOR_INFORMATION ); + } + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::~GlpiImpl + //----------------------------------------------------------------------------- + /*virtual*/ ~GlpiImpl() + { + free( m_pSlpi ); + m_pSlpi = 0; + m_nItems = 0; + } + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::IsDefaultImpl + //----------------------------------------------------------------------------- + /*virtual*/ BOOL IsDefaultImpl() const + { + return FALSE; + } + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::NumberOfProcessCores + // Desc: Gets the total number of physical processor cores available to the + // current process. + //----------------------------------------------------------------------------- + /*virtual*/ DWORD NumberOfProcessCores() const + { + DWORD_PTR dwProcessAffinity, dwSystemAffinity; + GetProcessAffinityMask( GetCurrentProcess(), &dwProcessAffinity, &dwSystemAffinity ); + + DWORD nCores = 0; + for( DWORD i = 0; i < m_nItems; ++i ) + { + if( ( RelationProcessorCore == m_pSlpi[i].Relationship ) && + ( m_pSlpi[i].ProcessorMask & dwProcessAffinity ) ) + { + ++nCores; + } + } + return nCores; + } + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::NumberOfSystemCores + // Desc: Gets the total number of physical processor cores enabled on the + // system. + //----------------------------------------------------------------------------- + /*virtual*/ DWORD NumberOfSystemCores() const + { + DWORD nCores = 0; + for( DWORD i = 0; i < m_nItems; ++i ) + { + if( RelationProcessorCore == m_pSlpi[i].Relationship ) + ++nCores; + } + return nCores; + } + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::CoreAffinityMask + // Desc: Gets an affinity mask that corresponds to the requested processor + // core. + //----------------------------------------------------------------------------- + /*virtual*/ DWORD_PTR CoreAffinityMask( DWORD coreIdx ) const + { + DWORD_PTR dwProcessAffinity, dwSystemAffinity; + GetProcessAffinityMask( GetCurrentProcess(), &dwProcessAffinity, &dwSystemAffinity ); + + for( DWORD i = 0; i < m_nItems; ++i ) + { + if( RelationProcessorCore == m_pSlpi[i].Relationship ) + { + if( !coreIdx-- ) + { + return m_pSlpi[i].ProcessorMask & dwProcessAffinity; + } + } + } + return 0; + } + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::IsSupported + //----------------------------------------------------------------------------- + static BOOL IsSupported() + { + return NULL != GetGlpiFn_(); + } + +private: + // GetLogicalProcessorInformation function pointer + typedef BOOL( WINAPI* GlpiFnPtr )( +SYSTEM_LOGICAL_PROCESSOR_INFORMATION*, +PDWORD +); + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::VerifyGlpiFn_ + // Desc: Gets a pointer to the GetLogicalProcessorInformation function only if + // it is supported on the current platform. + // GetLogicalProcessorInformation is supported on Windows Server 2003 and + // XP64, however there is a bug with the implementation. Therefore, only + // GetLogicalProcessorInformation on Windows Vista is supported in this + // sample. + //----------------------------------------------------------------------------- + static GlpiFnPtr VerifyGlpiFn_() + { + // VerifyVersionInfo function pointer + typedef BOOL ( WINAPI* VviFnPtr )( LPOSVERSIONINFOEX, + DWORD, + DWORDLONG ); + + HMODULE hMod = GetModuleHandle( TEXT( "kernel32" ) ); +#ifdef _UNICODE + VviFnPtr pVvi = (VviFnPtr) GetProcAddress( hMod, "VerifyVersionInfoW" ); + #else + VviFnPtr pVvi = ( VviFnPtr )GetProcAddress( hMod, "VerifyVersionInfoA" ); +#endif + GlpiFnPtr pGlpi = NULL; + + if( pVvi ) + { + // VerSetConditionMask function pointer + typedef ULONGLONG ( WINAPI* VscmFnPtr )( ULONGLONG, + DWORD, + BYTE ); + + VscmFnPtr pVscm = ( VscmFnPtr )GetProcAddress( hMod, "VerSetConditionMask" ); + + _ASSERT( pVscm ); + + // Check for Windows Vista + OSVERSIONINFOEX osvi = { sizeof( OSVERSIONINFOEX ) }; + osvi.dwMajorVersion = 6; + osvi.dwMinorVersion = 0; + osvi.wServicePackMajor = 0; + osvi.wServicePackMinor = 0; + + ULONGLONG dwlMask = 0; + dwlMask = pVscm( dwlMask, VER_MAJORVERSION, VER_GREATER_EQUAL ); + dwlMask = pVscm( dwlMask, VER_MINORVERSION, VER_GREATER_EQUAL ); + dwlMask = pVscm( dwlMask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL ); + dwlMask = pVscm( dwlMask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL ); + + if( pVvi( &osvi, VER_MAJORVERSION + | VER_MINORVERSION + | VER_SERVICEPACKMAJOR + | VER_SERVICEPACKMINOR, + dwlMask ) ) + { + pGlpi = ( GlpiFnPtr )GetProcAddress( hMod, "GetLogicalProcessorInformation" ); + _ASSERT( pGlpi ); + } + } + + return pGlpi; + + } + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::GetGlpiFn_ + // Desc: Gets a cached pointer to the GetLogicalProcessorInformation function. + //----------------------------------------------------------------------------- + static GlpiFnPtr GetGlpiFn_() + { + static GlpiFnPtr pGlpi = VerifyGlpiFn_(); + return pGlpi; + } + + // Private Members + SYSTEM_LOGICAL_PROCESSOR_INFORMATION* m_pSlpi; + DWORD m_nItems; +}; + +//--------------------------------------------------------------------------------- +// Name: ApicExtractor +// Desc: A utility class that provides an interface for decoding a processor +// APIC ID. An APIC ID is an 8-bit identifier given to each logical +// processor on system boot and can be retrieved by the CPUID instruction. +// Each APIC ID is composed of a PACKAGE_ID, CORE_ID and SMT_ID that describe +// the relationship of a logical processor within the processor topology of +// the system. +//--------------------------------------------------------------------------------- +class ApicExtractor +{ +public: + //----------------------------------------------------------------------------- + // Name: ApicExtractor::ApicExtractor + //----------------------------------------------------------------------------- + ApicExtractor( DWORD nLogProcsPerPkg = 1, DWORD nCoresPerPkg = 1 ) + { + SetPackageTopology( nLogProcsPerPkg, nCoresPerPkg ); + } + + //----------------------------------------------------------------------------- + // Name: ApicExtractor::SmtId + //----------------------------------------------------------------------------- + BYTE SmtId( BYTE apicId ) const + { + return apicId & m_smtIdMask.mask; + } + + //----------------------------------------------------------------------------- + // Name: ApicExtractor::CoreId + //----------------------------------------------------------------------------- + BYTE CoreId( BYTE apicId ) const + { + return ( apicId & m_coreIdMask.mask ) >> m_smtIdMask.width; + } + + //----------------------------------------------------------------------------- + // Name: ApicExtractor::PackageId + //----------------------------------------------------------------------------- + BYTE PackageId( BYTE apicId ) const + { + return ( apicId & m_pkgIdMask.mask ) >> + ( m_smtIdMask.width + m_coreIdMask.width ); + } + + //----------------------------------------------------------------------------- + // Name: ApicExtractor::PackageCoreId + //----------------------------------------------------------------------------- + BYTE PackageCoreId( BYTE apicId ) const + { + return ( apicId & ( m_pkgIdMask.mask | m_coreIdMask.mask ) ) >> + m_smtIdMask.width; + } + + //----------------------------------------------------------------------------- + // Name: ApicExtractor::GetLogProcsPerPkg + //----------------------------------------------------------------------------- + DWORD GetLogProcsPerPkg() const + { + return m_nLogProcsPerPkg; + } + + //----------------------------------------------------------------------------- + // Name: ApicExtractor::GetCoresPerPkg + //----------------------------------------------------------------------------- + DWORD GetCoresPerPkg() const + { + return m_nCoresPerPkg; + } + + //----------------------------------------------------------------------------- + // Name: ApicExtractor::SetPackageTopology + // Desc: You should call SetPackageTopology with the number of logical + // processors per package and number of cores per package before calling + // the sub id accessors (SmtId(), CoreId(), PackageId(), PackageCoreId()) + // as this information is required to effectively decode an APIC ID into + // its sub parts. + //----------------------------------------------------------------------------- + void SetPackageTopology( DWORD nLogProcsPerPkg, DWORD nCoresPerPkg ) + { + m_nLogProcsPerPkg = ( BYTE )nLogProcsPerPkg; + m_nCoresPerPkg = ( BYTE )nCoresPerPkg; + + // fix for Phenom x3 and similar CPUs - it reports 3 logical processors per package, and 4 cores per package + // so one core is probably just disabled for yield, but it causes a bug in GetMaskWidth that propagates + if( m_nCoresPerPkg > m_nLogProcsPerPkg ) + { + m_nCoresPerPkg = m_nLogProcsPerPkg; + } + + m_smtIdMask.width = GetMaskWidth_( m_nLogProcsPerPkg / m_nCoresPerPkg ); + m_coreIdMask.width = GetMaskWidth_( m_nCoresPerPkg ); + m_pkgIdMask.width = 8 - ( m_smtIdMask.width + m_coreIdMask.width ); + + m_pkgIdMask.mask = ( BYTE )( 0xFF << ( m_smtIdMask.width + m_coreIdMask.width ) ); + m_coreIdMask.mask = ( BYTE )( ( 0xFF << m_smtIdMask.width ) ^ m_pkgIdMask.mask ); + m_smtIdMask.mask = ( BYTE )~( 0xFF << m_smtIdMask.width ); + + } + +private: + //----------------------------------------------------------------------------- + // Name: ApicExtractor::GetMaskWidth_ + // Desc: Gets the width of a sub id bit field in an APIC ID. The width of a + // sub id (CORE_ID, SMT_ID) is only wide enough to support the maximum + // number of ids that needs to be represented in the topology. + //----------------------------------------------------------------------------- + static BYTE GetMaskWidth_( BYTE maxIds ) + { + --maxIds; + + // find index of msb + BYTE msbIdx = 8; + BYTE msbMask = 0x80; + while( msbMask && !( msbMask & maxIds ) ) + { + --msbIdx; + msbMask >>= 1; + } + return msbIdx; + } + + struct IdMask + { + BYTE width; + BYTE mask; + }; + + // Private Members + BYTE m_nLogProcsPerPkg; + BYTE m_nCoresPerPkg; + IdMask m_smtIdMask; + IdMask m_coreIdMask; + IdMask m_pkgIdMask; +}; + +//--------------------------------------------------------------------------------- +// Name: Cpuid +// Desc: A utility class that wraps the functionality of the CPUID instruction. +// Call the Call() method with the desired CPUID function, and use the +// register accessors to retrieve the register values. +//--------------------------------------------------------------------------------- +class Cpuid +{ +public: + // FnSet values are used to indicate a CPUID function set. + enum FnSet + { + Std = 0x00000000, + Ext = 0x80000000 + }; + + //----------------------------------------------------------------------------- + // Name: Cpuid::Cpuid + //----------------------------------------------------------------------------- + Cpuid() : m_eax( 0 ), + m_ebx( 0 ), + m_ecx( 0 ), + m_edx( 0 ) + { + } + + // Register accessors + DWORD Eax() const + { + return m_eax; + } + DWORD Ebx() const + { + return m_ebx; + } + DWORD Ecx() const + { + return m_ecx; + } + DWORD Edx() const + { + return m_edx; + } + + //----------------------------------------------------------------------------- + // Name: Cpuid::Call + // Desc: Calls the CPUID instruction with the specified function. Returns TRUE + // if the CPUID function was supported, FALSE if it wasn't. + //----------------------------------------------------------------------------- + BOOL Call( FnSet fnSet, DWORD fn ) + { + if( IsFnSupported( fnSet, fn ) ) + { + UncheckedCall_( fnSet, fn ); + return true; + } + return false; + } + + //----------------------------------------------------------------------------- + // Name: Cpuid::IsVendor + // Desc: Compares a string with the vendor string encoded in the CPUID + // instruction. + //----------------------------------------------------------------------------- + static BOOL IsVendor( const char* strVendor ) + { + // Cache the vendor string + static const Cpuid cpu( Std ); + return cpu.Ebx() == *reinterpret_cast<const DWORD*>( strVendor ) + && cpu.Ecx() == *reinterpret_cast<const DWORD*>( strVendor + 8 ) + && cpu.Edx() == *reinterpret_cast<const DWORD*>( strVendor + 4 ); + } + + //----------------------------------------------------------------------------- + // Name: Cpuid::IsFnSupported + // Desc: Checks to see if a CPUID function is supported. Different processors + // support different functions. This method is automatically called from + // the Call() method, so you don't need to call it beforehand. + //----------------------------------------------------------------------------- + static BOOL IsFnSupported( FnSet fnSet, DWORD fn ) + { + // Cache the maximum supported standard function + static const DWORD MaxStdFn = Cpuid( Std ).Eax(); + // Cache the maximum supported extended function + static const DWORD MaxExtFn = Cpuid( Ext ).Eax(); + + bool ret = false; + switch( fnSet ) + { + case Std: + ret = ( fn <= MaxStdFn ); + break; + case Ext: + ret = ( fn <= MaxExtFn ); + break; + default: + _ASSERT( 0 ); // should never get here + break; + } + return ret; + } + +private: + //----------------------------------------------------------------------------- + // Name: Cpuid::Cpuid + // Desc: This constructor is private and is only used to set a Cpuid object to + // initial values retrieved from CPUID functions 0x00000000 and + // 0x80000000. Good for caching values from the CPUID instruction that + // are not variable, like the encoded vendor string and the maximum + // supported CPUID function values. + //----------------------------------------------------------------------------- + explicit Cpuid( FnSet fnSet ) + { + UncheckedCall_( fnSet, 0 ); + } + + //----------------------------------------------------------------------------- + // Name: Cpuid::UncheckedCall_ + // Desc: Calls the CPUID instruction without checking for CPUID function + // support. + //----------------------------------------------------------------------------- + void UncheckedCall_( FnSet fnSet, DWORD fn ) + { +#ifdef _WIN64 + // Inline assembly is not supported in 64-bit. Ideally, we would prefer to + // use the __cpuid intrinsic to avoid having to drop to assembly altogether. + // However, as of MSVC 9.0, the __cpuid intrinsic does not enable the + // ability to set the ECX register, which is required for obtaining certain + // extended CPU information. To overcome these issues, we must call the + // Cpuid64() external function, which is written in assembly and located in + // the cpuid64.asm file included in this project. This project contains a build + // step that invokes 64-bit MASM on cpuid64.asm when building a 64-bit target. + m_eax = fnSet | fn; + m_ecx = 0; + Cpuid64(this); + #else + __asm + { + mov ecx, 0 + mov eax, fn + or eax, fnSet + cpuid + mov edi, this + mov [edi].m_eax, eax + mov [edi].m_ebx, ebx + mov [edi].m_ecx, ecx + mov [edi].m_edx, edx + } +#endif + } + + // Private Members + DWORD m_eax; + DWORD m_ebx; + DWORD m_ecx; + DWORD m_edx; +}; + +//--------------------------------------------------------------------------------- +// Name: CpuidImpl +// Desc: Provides the CPUID instruction implementation for the ICpuTopology +// interface. This is a ConcreteImplementor class in the traditional Bridge +// Pattern. +//--------------------------------------------------------------------------------- +class CpuidImpl : public ICpuTopology +{ +public: + // CpuidFnMasks are used when extracting bit-encoded information retrieved from + // the CPUID instruction + enum CpuidFnMasks + { + HTT = 0x10000000, // Fn0000_0001 EDX[28] + LogicalProcessorCount = 0x00FF0000, // Fn0000_0001 EBX[23:16] + ApicId = 0xFF000000, // Fn0000_0001 EBX[31:24] + NC_Intel = 0xFC000000, // Fn0000_0004 EAX[31:26] + NC_Amd = 0x000000FF, // Fn8000_0008 ECX[7:0] + CmpLegacy_Amd = 0x00000002, // Fn8000_0001 ECX[1] + ApicIdCoreIdSize_Amd = 0x0000F000 // Fn8000_0008 ECX[15:12] + }; + + enum + { + MaxLogicalProcessors = sizeof( DWORD_PTR ) * 8 + }; + + //----------------------------------------------------------------------------- + // Name: CpuidImpl::CpuidImpl + // Desc: Initializes internal structures/data with information retrieved from + // calling the CPUID instruction. + //----------------------------------------------------------------------------- + CpuidImpl() : m_nItems( 0 ) + { + _ASSERT( IsSupported() ); + + DWORD nLogProcsPerPkg = 1; + DWORD nCoresPerPkg = 1; + + Cpuid cpu; + + // Determine if hardware threading is enabled. + cpu.Call( Cpuid::Std, 1 ); + if( cpu.Edx() & HTT ) + { + // Determine the total number of logical processors per package. + nLogProcsPerPkg = ( cpu.Ebx() & LogicalProcessorCount ) >> 16; + + // Determine the total number of cores per package. This info + // is extracted differently dependending on the cpu vendor. + if( Cpuid::IsVendor( GenuineIntel ) ) + { + if( cpu.Call( Cpuid::Std, 4 ) ) + { + nCoresPerPkg = ( ( cpu.Eax() & NC_Intel ) >> 26 ) + 1; + } + } + else + { + _ASSERT( Cpuid::IsVendor( AuthenticAMD ) ); + if( cpu.Call( Cpuid::Ext, 8 ) ) + { + // AMD reports the msb width of the CORE_ID bit field of the APIC ID + // in ApicIdCoreIdSize_Amd. The maximum value represented by the msb + // width is the theoretical number of cores the processor can support + // and not the actual number of current cores, which is how the msb width + // of the CORE_ID bit field has been traditionally determined. If the + // ApicIdCoreIdSize_Amd value is zero, then you use the traditional method + // to determine the CORE_ID msb width. + DWORD msbWidth = cpu.Ecx() & ApicIdCoreIdSize_Amd; + if( msbWidth ) + { + // Set nCoresPerPkg to the maximum theortical number of cores + // the processor package can support (2 ^ width) so the APIC + // extractor object can be configured to extract the proper + // values from an APIC. + nCoresPerPkg = 1 << ( msbWidth >> 12 ); + } + else + { + // Set nCoresPerPkg to the actual number of cores being reported + // by the CPUID instruction. + nCoresPerPkg = ( cpu.Ecx() & NC_Amd ) + 1; + } + } + } + } + + // Configure the APIC extractor object with the information it needs to + // be able to decode the APIC. + m_apicExtractor.SetPackageTopology( nLogProcsPerPkg, nCoresPerPkg ); + + DWORD_PTR dwProcessAffinity, dwSystemAffinity; + HANDLE hProcess = GetCurrentProcess(); + HANDLE hThread = GetCurrentThread(); + GetProcessAffinityMask( hProcess, &dwProcessAffinity, &dwSystemAffinity ); + if( 1 == dwSystemAffinity ) + { + // Since we only have 1 logical processor present on the system, we + // can explicitly set a single APIC ID to zero. + _ASSERT( 1 == nLogProcsPerPkg ); + m_apicIds[m_nItems++] = 0; + } + else + { + // Set the process affinity to the system affinity if they are not + // equal so that all logical processors can be accounted for. + if( dwProcessAffinity != dwSystemAffinity ) + { + SetProcessAffinityMask( hProcess, dwSystemAffinity ); + } + + // Call cpuid on each active logical processor in the system affinity. + DWORD_PTR dwPrevThreadAffinity = 0; + for( DWORD_PTR dwThreadAffinity = 1; + dwThreadAffinity && dwThreadAffinity <= dwSystemAffinity; + dwThreadAffinity <<= 1 ) + { + if( dwSystemAffinity & dwThreadAffinity ) + { + if( 0 == dwPrevThreadAffinity ) + { + // Save the previous thread affinity so we can return + // the executing thread affinity back to this state. + _ASSERT( 0 == m_nItems ); + dwPrevThreadAffinity = SetThreadAffinityMask( hThread, + dwThreadAffinity ); + } + else + { + _ASSERT( m_nItems > 0 ); + SetThreadAffinityMask( hThread, dwThreadAffinity ); + } + + // Allow the thread to switch to masked logical processor. + Sleep( 0 ); + + // Store the APIC ID + cpu.Call( Cpuid::Std, 1 ); + m_apicIds[m_nItems++] = ( BYTE )( ( cpu.Ebx() & ApicId ) >> 24 ); + } + } + + // Restore the previous process and thread affinity state. + SetProcessAffinityMask( hProcess, dwProcessAffinity ); + SetThreadAffinityMask( hThread, dwPrevThreadAffinity ); + Sleep( 0 ); + } + + } + + //----------------------------------------------------------------------------- + // Name: CpuidImpl::IsDefaultImpl + //----------------------------------------------------------------------------- + /*virtual*/ BOOL IsDefaultImpl() const + { + return FALSE; + } + + //----------------------------------------------------------------------------- + // Name: CpuidImpl::NumberOfProcessCores + // Desc: Gets the number of processor cores available to the current process. + // The total accounts for cores that may have been masked out by process + // affinity. + //----------------------------------------------------------------------------- + /*virtual*/ DWORD NumberOfProcessCores() const + { + DWORD_PTR dwProcessAffinity, dwSystemAffinity; + GetProcessAffinityMask( GetCurrentProcess(), &dwProcessAffinity, &dwSystemAffinity ); + + BYTE pkgCoreIds[MaxLogicalProcessors] = { 0 }; + DWORD nPkgCoreIds = 0; + + for( DWORD i = 0; i < m_nItems; ++i ) + { + if( dwProcessAffinity & ( ( DWORD_PTR )1 << i ) ) + { + AddUniquePkgCoreId_( i, pkgCoreIds, nPkgCoreIds ); + } + } + return nPkgCoreIds; + } + + //----------------------------------------------------------------------------- + // Name: CpuidImpl::NumberOfSystemCores + // Desc: Gets the number of processor cores on the system. + //----------------------------------------------------------------------------- + /*virtual*/ DWORD NumberOfSystemCores() const + { + BYTE pkgCoreIds[MaxLogicalProcessors] = { 0 }; + DWORD nPkgCoreIds = 0; + for( DWORD i = 0; i < m_nItems; ++i ) + { + AddUniquePkgCoreId_( i, pkgCoreIds, nPkgCoreIds ); + } + return nPkgCoreIds; + } + + //----------------------------------------------------------------------------- + // Name: CpuidImpl::CoreAffinityMask + // Desc: Gets an affinity mask that corresponds to a specific processor core. + // coreIdx must be less than the total number of processor cores + // recognized by the operating system (NumberOfSystemCores()). + //----------------------------------------------------------------------------- + /*virtual*/ DWORD_PTR CoreAffinityMask( DWORD coreIdx ) const + { + BYTE pkgCoreIds[MaxLogicalProcessors] = { 0 }; + DWORD nPkgCoreIds = 0; + for( DWORD i = 0; i < m_nItems; ++i ) + { + AddUniquePkgCoreId_( i, pkgCoreIds, nPkgCoreIds ); + } + + DWORD_PTR dwProcessAffinity, dwSystemAffinity; + GetProcessAffinityMask( GetCurrentProcess(), &dwProcessAffinity, &dwSystemAffinity ); + + DWORD_PTR coreAffinity = 0; + if( coreIdx < nPkgCoreIds ) + { + for( DWORD i = 0; i < m_nItems; ++i ) + { + if( m_apicExtractor.PackageCoreId( m_apicIds[i] ) == pkgCoreIds[coreIdx] ) + { + coreAffinity |= ( dwProcessAffinity & ( ( DWORD_PTR )1 << i ) ); + } + } + } + return coreAffinity; + } + + //----------------------------------------------------------------------------- + // Name: CpuidImpl::IsSupported + // Desc: Indicates if a CpuidImpl object is supported on this platform. + // Support is only granted on Intel and AMD platforms where the current + // calling process has security rights to query process affinity and + // change it if the process and system affinity differ. CpuidImpl is + // also not supported if thread affinity cannot be set on systems with + // more than 1 logical processor. + //----------------------------------------------------------------------------- + static BOOL IsSupported() + { + BOOL bSupported = Cpuid::IsVendor( GenuineIntel ) + || Cpuid::IsVendor( AuthenticAMD ); + + if( bSupported ) + { + DWORD_PTR dwProcessAffinity, dwSystemAffinity; + HANDLE hProcess = GetCurrentProcess(); + + // Query process affinity mask + bSupported = GetProcessAffinityMask( hProcess, &dwProcessAffinity, &dwSystemAffinity ); + if( bSupported ) + { + if( dwProcessAffinity != dwSystemAffinity ) + { + // The process and system affinities differ. Attempt to set + // the process affinity to the system affinity. + bSupported = SetProcessAffinityMask( hProcess, dwSystemAffinity ); + if( bSupported ) + { + // Restore previous process affinity + bSupported = SetProcessAffinityMask( hProcess, dwProcessAffinity ); + } + } + + if( bSupported && ( dwSystemAffinity > 1 ) ) + { + // Attempt to set the thread affinity + HANDLE hThread = GetCurrentThread(); + DWORD_PTR dwThreadAffinity = SetThreadAffinityMask( hThread, dwProcessAffinity ); + if( dwThreadAffinity ) + { + // Restore the previous thread affinity + bSupported = 0 != SetThreadAffinityMask( hThread, dwThreadAffinity ); + } + else + { + bSupported = FALSE; + } + } + } + } + return bSupported; + } + +private: + + //----------------------------------------------------------------------------- + // Name: CpuidImpl::AddUniquePkgCoreId_ + // Desc: Adds the package/core id extracted from the APIC ID at m_apicIds[idx] + // in the if the package/core id is unique to the pkgCoreIds array. + // nPkgCore is an in/out parm that will reflect the total number of items + // in pkgCoreIds array. It will be incrememted if a unique package/core + // id is found and added. + //----------------------------------------------------------------------------- + void AddUniquePkgCoreId_( DWORD idx, BYTE* pkgCoreIds, DWORD& nPkgCoreIds ) const + { + _ASSERT( idx < m_nItems ); + _ASSERT( NULL != pkgCoreIds ); + + DWORD j; + for( j = 0; j < nPkgCoreIds; ++j ) + { + if( pkgCoreIds[j] == m_apicExtractor.PackageCoreId( m_apicIds[idx] ) ) + break; + } + if( j == nPkgCoreIds ) + { + pkgCoreIds[j] = m_apicExtractor.PackageCoreId( m_apicIds[idx] ); + ++nPkgCoreIds; + } + } + + // Private Members + BYTE m_apicIds[MaxLogicalProcessors]; + BYTE m_nItems; + ApicExtractor m_apicExtractor; + + // Supported Vendor Strings + static const char GenuineIntel[]; + static const char AuthenticAMD[]; +}; + +// Static initialization of vendor strings +const char CpuidImpl::GenuineIntel[] = "GenuineIntel"; +const char CpuidImpl::AuthenticAMD[] = "AuthenticAMD"; + +} // unnamed-namespace + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::CpuTopology +// Desc: Initializes this object with the appropriately supported cpu topology +// implementation object. +//------------------------------------------------------------------------------------- +CpuTopology::CpuTopology( BOOL bForceCpuid ) : m_pImpl( NULL ) +{ + ForceCpuid( bForceCpuid ); +} + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::~CpuTopology +//------------------------------------------------------------------------------------- +CpuTopology::~CpuTopology() +{ + Destroy_(); +} + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::NumberOfProcessCores +// Desc: Gets the total number of physical processor cores available to the current +// process. +//------------------------------------------------------------------------------------- +DWORD CpuTopology::NumberOfProcessCores() const +{ + return m_pImpl->NumberOfProcessCores(); +} + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::NumberOfSystemCores +// Desc: Gets the total number of physical processor cores enabled on the system. +//------------------------------------------------------------------------------------- +DWORD CpuTopology::NumberOfSystemCores() const +{ + return m_pImpl->NumberOfSystemCores(); +} + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::CoreAffinityMask +// Desc: Gets an affinity mask that corresponds to the requested processor core. +//------------------------------------------------------------------------------------- +DWORD_PTR CpuTopology::CoreAffinityMask( DWORD coreIdx ) const +{ + return m_pImpl->CoreAffinityMask( coreIdx ); +} + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::IsDefaultImpl +// Desc: Returns TRUE if m_pImpl is a DefaultImpl object, FALSE if not. Used to +// indicate whether or not the prescribed methods (CPUID or +// GetLogicalProcessorInformation) are supported on the system. +//------------------------------------------------------------------------------------- +BOOL CpuTopology::IsDefaultImpl() const +{ + return m_pImpl->IsDefaultImpl(); +} + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::ForceCpuid +// Desc: Constructs a cpu topology object. If bForce is FALSE, then a GlpiImpl object +// is first attempted, then CpuidImpl, then finally DefaultImpl. If bForce is +// TRUE, then GlpiImpl is never attempted. +//------------------------------------------------------------------------------------- +void CpuTopology::ForceCpuid( BOOL bForce ) +{ + Destroy_(); + + if( !bForce && GlpiImpl::IsSupported() ) + { + m_pImpl = new GlpiImpl(); + } + else if( CpuidImpl::IsSupported() ) + { + m_pImpl = new CpuidImpl(); + } + else + { + m_pImpl = new DefaultImpl(); + } +} + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::Destroy_ +//------------------------------------------------------------------------------------- +void CpuTopology::Destroy_() +{ + delete m_pImpl; + m_pImpl = NULL; +} +#endif
\ No newline at end of file diff --git a/external/vpc/tier0/cputopology.h b/external/vpc/tier0/cputopology.h new file mode 100644 index 0000000..5da2890 --- /dev/null +++ b/external/vpc/tier0/cputopology.h @@ -0,0 +1,40 @@ +//------------------------------------------------------------------------------------- +// CpuTopology.h +// +// CpuToplogy class declaration. +// +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------------------------------- +#pragma once +#ifndef CPU_TOPOLOGY_H +#define CPU_TOPOLOGY_H + +#include "winlite.h" + +class ICpuTopology; + +//--------------------------------------------------------------------------------- +// Name: CpuToplogy +// Desc: This class constructs a supported cpu topology implementation object on +// initialization and forwards calls to it. This is the Abstraction class +// in the traditional Bridge Pattern. +//--------------------------------------------------------------------------------- +class CpuTopology +{ +public: + CpuTopology( BOOL bForceCpuid = FALSE ); + ~CpuTopology(); + + BOOL IsDefaultImpl() const; + DWORD NumberOfProcessCores() const; + DWORD NumberOfSystemCores() const; + DWORD_PTR CoreAffinityMask( DWORD coreIdx ) const; + + void ForceCpuid( BOOL bForce ); +private: + void Destroy_(); + + ICpuTopology* m_pImpl; +}; + +#endif // CPU_TOPOLOGY_H diff --git a/external/vpc/tier0/dbg.cpp b/external/vpc/tier0/dbg.cpp new file mode 100644 index 0000000..8effafb --- /dev/null +++ b/external/vpc/tier0/dbg.cpp @@ -0,0 +1,627 @@ +//===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// + +#include "tier0/platform.h" + +#if defined( PLATFORM_WINDOWS_PC ) +#define WIN_32_LEAN_AND_MEAN +#include <windows.h> // Currently needed for IsBadReadPtr and IsBadWritePtr +#pragma comment(lib,"user32.lib") // For MessageBox +#endif + +#include "tier0/minidump.h" +#include "tier0/stacktools.h" + +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> +#include "color.h" +#include "tier0/dbg.h" +#include "tier0/threadtools.h" +#include "tier0/icommandline.h" +#include <math.h> + +#if defined( _X360 ) +#include "xbox/xbox_console.h" +#endif + +#include "tier0/etwprof.h" + +#ifndef STEAM +#define PvRealloc realloc +#define PvAlloc malloc +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) +#pragma optimize( "g", off ) //variable argument functions seem to screw up stack walking unless this optimization is disabled +#pragma warning( disable: 4748 ) // Turn off the warning telling us that optimizations are off if /GS is on +#endif + +DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_LOADING, "LOADING" ); + +//----------------------------------------------------------------------------- +// Stack attachment management +//----------------------------------------------------------------------------- +#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + +static bool s_bCallStacksWithAllWarnings = false; //if true, attach a call stack to every SPEW_WARNING message. Warning()/DevWarning()/... +static int s_iWarningMaxCallStackLength = 5; +#define AutomaticWarningCallStackLength() (s_bCallStacksWithAllWarnings ? s_iWarningMaxCallStackLength : 0) + +void _Warning_AlwaysSpewCallStack_Enable( bool bEnable ) +{ + s_bCallStacksWithAllWarnings = bEnable; +} + +void _Warning_AlwaysSpewCallStack_Length( int iMaxCallStackLength ) +{ + s_iWarningMaxCallStackLength = iMaxCallStackLength; +} + +static bool s_bCallStacksWithAllErrors = false; //if true, attach a call stack to every SPEW_ERROR message. Mostly just Error() +static int s_iErrorMaxCallStackLength = 20; //default to higher output with an error since we're quitting anyways +#define AutomaticErrorCallStackLength() (s_bCallStacksWithAllErrors ? s_iErrorMaxCallStackLength : 0) + +void _Error_AlwaysSpewCallStack_Enable( bool bEnable ) +{ + s_bCallStacksWithAllErrors = bEnable; +} + +void _Error_AlwaysSpewCallStack_Length( int iMaxCallStackLength ) +{ + s_iErrorMaxCallStackLength = iMaxCallStackLength; +} + +#else //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + +#define AutomaticWarningCallStackLength() 0 +#define AutomaticErrorCallStackLength() 0 + +void _Warning_AlwaysSpewCallStack_Enable( bool bEnable ) +{ +} + +void _Warning_AlwaysSpewCallStack_Length( int iMaxCallStackLength ) +{ +} + +void _Error_AlwaysSpewCallStack_Enable( bool bEnable ) +{ +} + +void _Error_AlwaysSpewCallStack_Length( int iMaxCallStackLength ) +{ +} + +#endif //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + +void _ExitOnFatalAssert( const tchar* pFile, int line ) +{ + Log_Msg( LOG_ASSERT, _T("Fatal assert failed: %s, line %d. Application exiting.\n"), pFile, line ); + + // only write out minidumps if we're not in the debugger + if ( !Plat_IsInDebugSession() ) + { + WriteMiniDump(); + } + + Log_Msg( LOG_DEVELOPER, _T("_ExitOnFatalAssert\n") ); + Plat_ExitProcess( EXIT_FAILURE ); +} + + +//----------------------------------------------------------------------------- +// Templates to assist in validating pointers: +//----------------------------------------------------------------------------- +PLATFORM_INTERFACE void _AssertValidReadPtr( void* ptr, int count/* = 1*/ ) +{ +#if defined( _WIN32 ) && !defined( _X360 ) + Assert( !IsBadReadPtr( ptr, count ) ); +#else + Assert( !count || ptr ); +#endif +} + +PLATFORM_INTERFACE void _AssertValidWritePtr( void* ptr, int count/* = 1*/ ) +{ +#if defined( _WIN32 ) && !defined( _X360 ) + Assert( !IsBadWritePtr( ptr, count ) ); +#else + Assert( !count || ptr ); +#endif +} + +PLATFORM_INTERFACE void _AssertValidReadWritePtr( void* ptr, int count/* = 1*/ ) +{ +#if defined( _WIN32 ) && !defined( _X360 ) + Assert(!( IsBadWritePtr(ptr, count) || IsBadReadPtr(ptr,count))); +#else + Assert( !count || ptr ); +#endif +} + +PLATFORM_INTERFACE void _AssertValidStringPtr( const tchar* ptr, int maxchar/* = 0xFFFFFF */ ) +{ +#if defined( _WIN32 ) && !defined( _X360 ) + #ifdef TCHAR_IS_CHAR + Assert( !IsBadStringPtr( ptr, maxchar ) ); + #else + Assert( !IsBadStringPtrW( ptr, maxchar ) ); + #endif +#else + Assert( ptr ); +#endif +} + +void AppendCallStackToLogMessage( tchar *formattedMessage, int iMessageLength, int iAppendCallStackLength ) +{ +#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) +# if defined( TCHAR_IS_CHAR ) //I'm horrible with unicode and I don't plan on testing this with wide characters just yet + if( iAppendCallStackLength > 0 ) + { + int iExistingMessageLength = (int)strlen( formattedMessage ); //no V_strlen in tier 0, plus we're only compiling this for windows and 360. Seems safe + formattedMessage += iExistingMessageLength; + iMessageLength -= iExistingMessageLength; + + if( iMessageLength <= 32 ) + return; //no room for anything useful + + //append directly to the spew message + if( (iExistingMessageLength > 0) && (formattedMessage[-1] == '\n') ) + { + --formattedMessage; + ++iMessageLength; + } + + //append preface + int iAppendedLength = _snprintf( formattedMessage, iMessageLength, _T("\nCall Stack:\n\t") ); + + void **CallStackBuffer = (void **)stackalloc( iAppendCallStackLength * sizeof( void * ) ); + int iCount = GetCallStack( CallStackBuffer, iAppendCallStackLength, 2 ); + if( TranslateStackInfo( CallStackBuffer, iCount, formattedMessage + iAppendedLength, iMessageLength - iAppendedLength, _T("\n\t") ) == 0 ) + { + //failure + formattedMessage[0] = '\0'; //this is pointing at where we wrote "\nCall Stack:\n\t" + } + else + { + iAppendedLength += (int)strlen( formattedMessage + iAppendedLength ); //no V_strlen in tier 0, plus we're only compiling this for windows and 360. Seems safe + + if( iAppendedLength < iMessageLength ) + { + formattedMessage[iAppendedLength] = '\n'; //Add another newline. + ++iAppendedLength; + + formattedMessage[iAppendedLength] = '\0'; + } + } + } +# else + AssertMsg( false, "Fixme" ); +# endif +#endif +} + +// Forward declare for internal use only. +CLoggingSystem *GetGlobalLoggingSystem(); + +#define Log_LegacyHelperColor_Stack( Channel, Severity, Color, MessageFormat, AppendCallStackLength ) \ + do \ +{ \ + CLoggingSystem *pLoggingSystem = GetGlobalLoggingSystem(); \ + if ( pLoggingSystem->IsChannelEnabled( Channel, Severity ) ) \ +{ \ + tchar formattedMessage[MAX_LOGGING_MESSAGE_LENGTH]; \ + va_list args; \ + va_start( args, MessageFormat ); \ + Tier0Internal_vsntprintf( formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, MessageFormat, args ); \ + va_end( args ); \ + AppendCallStackToLogMessage( formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, AppendCallStackLength ); \ + pLoggingSystem->LogDirect( Channel, Severity, Color, formattedMessage ); \ +} \ +} while( 0 ) + +#define Log_LegacyHelperColor( Channel, Severity, Color, MessageFormat ) Log_LegacyHelperColor_Stack( Channel, Severity, Color, MessageFormat, 0 ) + +#define Log_LegacyHelper_Stack( Channel, Severity, MessageFormat, AppendCallStackLength ) Log_LegacyHelperColor_Stack( Channel, Severity, pLoggingSystem->GetChannelColor( Channel ), MessageFormat, AppendCallStackLength ) +#define Log_LegacyHelper( Channel, Severity, MessageFormat ) Log_LegacyHelperColor( Channel, Severity, pLoggingSystem->GetChannelColor( Channel ), MessageFormat ) + + +void Msg( const tchar* pMsgFormat, ... ) +{ + Log_LegacyHelper( LOG_GENERAL, LS_MESSAGE, pMsgFormat ); +} + +void Warning( const tchar *pMsgFormat, ... ) +{ + Log_LegacyHelper_Stack( LOG_GENERAL, LS_WARNING, pMsgFormat, AutomaticWarningCallStackLength() ); +} + +void Warning_SpewCallStack( int iMaxCallStackLength, const tchar *pMsgFormat, ... ) +{ + Log_LegacyHelper_Stack( LOG_GENERAL, LS_WARNING, pMsgFormat, iMaxCallStackLength ); +} + + +void Error( const tchar *pMsgFormat, ... ) +{ + Log_LegacyHelper_Stack( LOG_GENERAL, LS_ERROR, pMsgFormat, AutomaticErrorCallStackLength() ); +} + +void Error_SpewCallStack( int iMaxCallStackLength, const tchar *pMsgFormat, ... ) +{ + Log_LegacyHelper_Stack( LOG_GENERAL, LS_ERROR, pMsgFormat, iMaxCallStackLength ); +} + + +//----------------------------------------------------------------------------- +// A couple of super-common dynamic spew messages, here for convenience +// These looked at the "developer" group, print if it's level 1 or higher +//----------------------------------------------------------------------------- +void DevMsg( int level, const tchar* pMsgFormat, ... ) +{ + LoggingChannelID_t channel = level >= 2 ? LOG_DEVELOPER_VERBOSE : LOG_DEVELOPER; + Log_LegacyHelper( channel, LS_MESSAGE, pMsgFormat ); +} + + +void DevWarning( int level, const tchar *pMsgFormat, ... ) +{ + LoggingChannelID_t channel = level >= 2 ? LOG_DEVELOPER_VERBOSE : LOG_DEVELOPER; + Log_LegacyHelper( channel, LS_WARNING, pMsgFormat ); +} + +void DevMsg( const tchar *pMsgFormat, ... ) +{ + Log_LegacyHelper( LOG_DEVELOPER, LS_MESSAGE, pMsgFormat ); +} + +void DevWarning( const tchar *pMsgFormat, ... ) +{ + Log_LegacyHelper( LOG_DEVELOPER, LS_WARNING, pMsgFormat ); +} + +void ConColorMsg( const Color& clr, const tchar* pMsgFormat, ... ) +{ + Log_LegacyHelperColor( LOG_CONSOLE, LS_MESSAGE, clr, pMsgFormat ); +} + +void ConMsg( const tchar *pMsgFormat, ... ) +{ + Log_LegacyHelper( LOG_CONSOLE, LS_MESSAGE, pMsgFormat ); +} + +void ConDMsg( const tchar *pMsgFormat, ... ) +{ + Log_LegacyHelper( LOG_DEVELOPER_CONSOLE, LS_MESSAGE, pMsgFormat ); +} + +// If we don't have a function from math.h, then it doesn't link certain floating-point +// functions in and printfs with %f cause runtime errors in the C libraries. +PLATFORM_INTERFACE float CrackSmokingCompiler( float a ) +{ + return (float)fabs( a ); +} + +void* Plat_SimpleLog( const tchar* file, int line ) +{ + FILE* f = _tfopen( _T("simple.log"), _T("at+") ); + _ftprintf( f, _T("%s:%i\n"), file, line ); + fclose( f ); + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: For debugging startup times, etc. +// Input : *fmt - +// ... - +//----------------------------------------------------------------------------- +void COM_TimestampedLog( char const *fmt, ... ) +{ + static float s_LastStamp = 0.0; + static bool s_bShouldLog = false; + static bool s_bShouldLogToConsole = false; + static bool s_bShouldLogToETW = false; + static bool s_bChecked = false; + static bool s_bFirstWrite = false; + + if ( !s_bChecked ) + { + s_bShouldLog = ( CommandLine()->CheckParm( "-profile" ) ) ? true : false; + s_bShouldLogToConsole = ( CommandLine()->ParmValue( "-profile", 0.0f ) != 0.0f ) ? true : false; + + s_bShouldLogToETW = ( CommandLine()->CheckParm( "-etwprofile" ) ) ? true : false; + if ( s_bShouldLogToETW ) + { + s_bShouldLog = true; + } + + s_bChecked = true; + } + if ( !s_bShouldLog ) + { + return; + } + + char string[1024]; + va_list argptr; + va_start( argptr, fmt ); + Tier0Internal_vsnprintf( string, sizeof( string ), fmt, argptr ); + va_end( argptr ); + + float curStamp = Plat_FloatTime(); + +#if defined( _X360 ) + XBX_rTimeStampLog( curStamp, string ); +#elif defined( _PS3 ) + Log_Warning( LOG_LOADING, "%8.4f / %8.4f: %s\n", curStamp, curStamp - s_LastStamp, string ); +#endif + + if ( IsPC() ) + { + // If ETW profiling is enabled then do it only. + if ( s_bShouldLogToETW ) + { + ETWMark( string ); + } + else + { + if ( !s_bFirstWrite ) + { + unlink( "timestamped.log" ); + s_bFirstWrite = true; + } + + FILE* fp = fopen( "timestamped.log", "at+" ); + fprintf( fp, "%8.4f / %8.4f: %s\n", curStamp, curStamp - s_LastStamp, string ); + fclose( fp ); + } + + if ( s_bShouldLogToConsole ) + { + Msg( "%8.4f / %8.4f: %s\n", curStamp, curStamp - s_LastStamp, string ); + } + } + + s_LastStamp = curStamp; +} + +#ifdef IS_WINDOWS_PC + +class CHardwareBreakPoint +{ +public: + + enum EOpCode + { + BRK_SET = 0, + BRK_UNSET, + }; + + CHardwareBreakPoint() + { + m_eOperation = BRK_SET; + m_pvAddress = 0; + m_hThread = 0; + m_hThreadEvent = 0; + m_nRegister = 0; + m_bSuccess = false; + } + + const void *m_pvAddress; + HANDLE m_hThread; + EHardwareBreakpointType m_eType; + EHardwareBreakpointSize m_eSize; + HANDLE m_hThreadEvent; + int m_nRegister; + EOpCode m_eOperation; + bool m_bSuccess; + + static void SetBits( DWORD_PTR& dw, int lowBit, int bits, int newValue ); + static DWORD WINAPI ThreadProc( LPVOID lpParameter ); +}; + +void CHardwareBreakPoint::SetBits( DWORD_PTR& dw, int lowBit, int bits, int newValue ) +{ + DWORD_PTR mask = (1 << bits) - 1; + dw = (dw & ~(mask << lowBit)) | (newValue << lowBit); +} + +DWORD WINAPI CHardwareBreakPoint::ThreadProc( LPVOID lpParameter ) +{ + CHardwareBreakPoint *h = reinterpret_cast< CHardwareBreakPoint * >( lpParameter ); + SuspendThread( h->m_hThread ); + + // Get current context + CONTEXT ct = {0}; + ct.ContextFlags = CONTEXT_DEBUG_REGISTERS; + GetThreadContext(h->m_hThread,&ct); + + int FlagBit = 0; + + bool Dr0Busy = false; + bool Dr1Busy = false; + bool Dr2Busy = false; + bool Dr3Busy = false; + if (ct.Dr7 & 1) + Dr0Busy = true; + if (ct.Dr7 & 4) + Dr1Busy = true; + if (ct.Dr7 & 16) + Dr2Busy = true; + if (ct.Dr7 & 64) + Dr3Busy = true; + + if ( h->m_eOperation == CHardwareBreakPoint::BRK_UNSET ) + { + // Remove + if (h->m_nRegister == 0) + { + FlagBit = 0; + ct.Dr0 = 0; + Dr0Busy = false; + } + if (h->m_nRegister == 1) + { + FlagBit = 2; + ct.Dr1 = 0; + Dr1Busy = false; + } + if (h->m_nRegister == 2) + { + FlagBit = 4; + ct.Dr2 = 0; + Dr2Busy = false; + } + if (h->m_nRegister == 3) + { + FlagBit = 6; + ct.Dr3 = 0; + Dr3Busy = false; + } + ct.Dr7 &= ~(1 << FlagBit); + } + else + { + if (!Dr0Busy) + { + h->m_nRegister = 0; + ct.Dr0 = (DWORD_PTR)h->m_pvAddress; + Dr0Busy = true; + } + else if (!Dr1Busy) + { + h->m_nRegister = 1; + ct.Dr1 = (DWORD_PTR)h->m_pvAddress; + Dr1Busy = true; + } + else if (!Dr2Busy) + { + h->m_nRegister = 2; + ct.Dr2 = (DWORD_PTR)h->m_pvAddress; + Dr2Busy = true; + } + else if (!Dr3Busy) + { + h->m_nRegister = 3; + ct.Dr3 = (DWORD_PTR)h->m_pvAddress; + Dr3Busy = true; + } + else + { + h->m_bSuccess = false; + ResumeThread(h->m_hThread); + SetEvent(h->m_hThreadEvent); + return 0; + } + + ct.Dr6 = 0; + int st = 0; + if (h->m_eType == BREAKPOINT_EXECUTE) + st = 0; + if (h->m_eType == BREAKPOINT_READWRITE) + st = 3; + if (h->m_eType == BREAKPOINT_WRITE) + st = 1; + + int le = 0; + if (h->m_eSize == BREAKPOINT_SIZE_1) + le = 0; + if (h->m_eSize == BREAKPOINT_SIZE_2) + le = 1; + if (h->m_eSize == BREAKPOINT_SIZE_4) + le = 3; + if (h->m_eSize == BREAKPOINT_SIZE_8) + le = 2; + + SetBits( ct.Dr7, 16 + h->m_nRegister*4, 2, st ); + SetBits( ct.Dr7, 18 + h->m_nRegister*4, 2, le ); + SetBits( ct.Dr7, h->m_nRegister*2,1,1); + } + + ct.ContextFlags = CONTEXT_DEBUG_REGISTERS; + SetThreadContext(h->m_hThread,&ct); + + ResumeThread( h->m_hThread ); + h->m_bSuccess = true; + SetEvent( h->m_hThreadEvent ); + return 0; +} + +HardwareBreakpointHandle_t SetHardwareBreakpoint( EHardwareBreakpointType eType, EHardwareBreakpointSize eSize, const void *pvLocation ) +{ + CHardwareBreakPoint *h = new CHardwareBreakPoint(); + h->m_pvAddress = pvLocation; + h->m_eSize = eSize; + h->m_eType = eType; + HANDLE hThread = GetCurrentThread(); + h->m_hThread = hThread; + + if ( hThread == GetCurrentThread() ) + { + DWORD nThreadId = GetCurrentThreadId(); + h->m_hThread = OpenThread( THREAD_ALL_ACCESS, 0, nThreadId ); + } + + h->m_hThreadEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + h->m_eOperation = CHardwareBreakPoint::BRK_SET; // Set Break + CreateThread( 0, 0, CHardwareBreakPoint::ThreadProc, (LPVOID)h, 0, 0 ); + WaitForSingleObject( h->m_hThreadEvent,INFINITE ); + CloseHandle( h->m_hThreadEvent ); + h->m_hThreadEvent = 0; + if ( hThread == GetCurrentThread() ) + { + CloseHandle( h->m_hThread ); + } + h->m_hThread = hThread; + if ( !h->m_bSuccess ) + { + delete h; + return (HardwareBreakpointHandle_t)0; + } + return (HardwareBreakpointHandle_t)h; +} + +bool ClearHardwareBreakpoint( HardwareBreakpointHandle_t handle ) +{ + CHardwareBreakPoint *h = reinterpret_cast< CHardwareBreakPoint* >( handle ); + if ( !h ) + { + return false; + } + + bool bOpened = false; + if ( h->m_hThread == GetCurrentThread() ) + { + DWORD nThreadId = GetCurrentThreadId(); + h->m_hThread = OpenThread( THREAD_ALL_ACCESS, 0, nThreadId ); + bOpened = true; + } + + h->m_hThreadEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + h->m_eOperation = CHardwareBreakPoint::BRK_UNSET; // Remove Break + CreateThread( 0,0,CHardwareBreakPoint::ThreadProc, (LPVOID)h, 0,0 ); + WaitForSingleObject( h->m_hThreadEvent, INFINITE ); + CloseHandle( h->m_hThreadEvent ); + h->m_hThreadEvent = 0; + if ( bOpened ) + { + CloseHandle( h->m_hThread ); + } + delete h; + return true; +} + +#endif // IS_WINDOWS_PC + diff --git a/external/vpc/tier0/etwprof.cpp b/external/vpc/tier0/etwprof.cpp new file mode 100644 index 0000000..ccd2c76 --- /dev/null +++ b/external/vpc/tier0/etwprof.cpp @@ -0,0 +1,377 @@ +//============ Copyright (c) Valve Corporation, All rights reserved. ============ +// +// ETW (Event Tracing for Windows) profiling helpers. +// This allows easy insertion of Generic Event markers into ETW/xperf tracing +// which then aids in analyzing the traces and finding performance problems. +// +//=============================================================================== + +#include "pch_tier0.h" +#include "tier0/etwprof.h" +#include <memory> + +#ifdef ETW_MARKS_ENABLED + +// After building the DLL if it has never been registered on this machine or +// if the providers have changed you need to go: +// xcopy /y %vgame%\bin\tier0.dll %temp% +// wevtutil um %vgame%\..\src\tier0\ValveETWProvider.man +// wevtutil im %vgame%\..\src\tier0\ValveETWProvider.man + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +// These are defined in evntrace.h but you need a Vista+ Windows +// SDK to have them available, so I define them here. +#define EVENT_CONTROL_CODE_DISABLE_PROVIDER 0 +#define EVENT_CONTROL_CODE_ENABLE_PROVIDER 1 +#define EVENT_CONTROL_CODE_CAPTURE_STATE 2 + +// EVNTAPI is used in evntprov.h which is included by ValveETWProviderEvents.h +// We define EVNTAPI without the DECLSPEC_IMPORT specifier so that +// we can implement these functions locally instead of using the import library, +// and can therefore still run on Windows XP. +#define EVNTAPI __stdcall +// Include the event register/write/unregister macros compiled from the manifest file. +// Note that this includes evntprov.h which requires a Vista+ Windows SDK +// which we don't currently have, so evntprov.h is checked in. +#include "ValveETWProviderEvents.h" + +// Typedefs for use with GetProcAddress +typedef ULONG (__stdcall *tEventRegister)( LPCGUID ProviderId, PENABLECALLBACK EnableCallback, PVOID CallbackContext, PREGHANDLE RegHandle); +typedef ULONG (__stdcall *tEventWrite)( REGHANDLE RegHandle, PCEVENT_DESCRIPTOR EventDescriptor, ULONG UserDataCount, PEVENT_DATA_DESCRIPTOR UserData); +typedef ULONG (__stdcall *tEventUnregister)( REGHANDLE RegHandle ); + +// Helper class to dynamically load Advapi32.dll, find the ETW functions, +// register the providers if possible, and get the performance counter frequency. +class CETWRegister +{ +public: + CETWRegister() + { + QueryPerformanceFrequency( &m_frequency ); + + // Find Advapi32.dll. This should always succeed. + HMODULE pAdvapiDLL = LoadLibraryW( L"Advapi32.dll" ); + if ( pAdvapiDLL ) + { + // Try to find the ETW functions. This will fail on XP. + m_pEventRegister = ( tEventRegister )GetProcAddress( pAdvapiDLL, "EventRegister" ); + m_pEventWrite = ( tEventWrite )GetProcAddress( pAdvapiDLL, "EventWrite" ); + m_pEventUnregister = ( tEventUnregister )GetProcAddress( pAdvapiDLL, "EventUnregister" ); + + // Register two ETW providers. If registration fails then the event logging calls will fail. + // On XP these calls will do nothing. + // On Vista and above, if these providers have been enabled by xperf or logman then + // the VALVE_FRAMERATE_Context and VALVE_MAIN_Context globals will be modified + // like this: + // MatchAnyKeyword: 0xffffffffffffffff + // IsEnabled: 1 + // Level: 255 + // In other words, fully enabled. + + EventRegisterValve_FrameRate(); + EventRegisterValve_ServerFrameRate(); + EventRegisterValve_Main(); + EventRegisterValve_Input(); + EventRegisterValve_Network(); + + // Emit the thread ID for the main thread. This also indicates that + // the main provider is initialized. + EventWriteThread_ID( GetCurrentThreadId(), "Main thread" ); + // Emit an input system event so we know that it is active. + EventWriteKey_down( "Valve input provider initialized.", 0, 0 ); + } + } + ~CETWRegister() + { + // Unregister our providers. + EventUnregisterValve_Network(); + EventUnregisterValve_Input(); + EventUnregisterValve_Main(); + EventUnregisterValve_ServerFrameRate(); + EventUnregisterValve_FrameRate(); + } + + tEventRegister m_pEventRegister; + tEventWrite m_pEventWrite; + tEventUnregister m_pEventUnregister; + + // QPC frequency + LARGE_INTEGER m_frequency; + +} g_ETWRegister; + +// Redirector function for EventRegister. Called by macros in ValveETWProviderEvents.h +ULONG EVNTAPI EventRegister( LPCGUID ProviderId, PENABLECALLBACK EnableCallback, PVOID CallbackContext, PREGHANDLE RegHandle ) +{ + if ( g_ETWRegister.m_pEventRegister ) + return g_ETWRegister.m_pEventRegister( ProviderId, EnableCallback, CallbackContext, RegHandle ); + + return 0; +} + +// Redirector function for EventWrite. Called by macros in ValveETWProviderEvents.h +ULONG EVNTAPI EventWrite( REGHANDLE RegHandle, PCEVENT_DESCRIPTOR EventDescriptor, ULONG UserDataCount, PEVENT_DATA_DESCRIPTOR UserData ) +{ + if ( g_ETWRegister.m_pEventWrite ) + return g_ETWRegister.m_pEventWrite( RegHandle, EventDescriptor, UserDataCount, UserData ); + return 0; +} + +// Redirector function for EventUnregister. Called by macros in ValveETWProviderEvents.h +ULONG EVNTAPI EventUnregister( REGHANDLE RegHandle ) +{ + if ( g_ETWRegister.m_pEventUnregister ) + return g_ETWRegister.m_pEventUnregister( RegHandle ); + return 0; +} + +// Call QueryPerformanceCounter +static int64 GetQPCTime() +{ + LARGE_INTEGER time; + + QueryPerformanceCounter( &time ); + return time.QuadPart; +} + +// Convert a QueryPerformanceCounter delta into milliseconds +static float QPCToMS( int64 nDelta ) +{ + // Convert from a QPC delta to seconds. + float flSeconds = ( float )( nDelta / double( g_ETWRegister.m_frequency.QuadPart ) ); + + // Convert from seconds to milliseconds + return flSeconds * 1000; +} + +// Public functions for emitting ETW events. + +int64 ETWMark( const char *pMessage ) +{ + int64 nTime = GetQPCTime(); + EventWriteMark( pMessage ); + return nTime; +} + +int64 ETWMarkPrintf( const char *pMessage, ... ) +{ + // If we are running on Windows XP or if our providers have not been enabled + // (by xperf or other) then this will be false and we can early out. + // Be sure to check the appropriate context for the event. This is only + // worth checking if there is some cost beyond the EventWrite that we can + // avoid -- the redirectors in this file guarantee that EventWrite is always + // safe to call. + if ( !VALVE_MAIN_Context.IsEnabled ) + { + return 0; + } + + char buffer[1000]; + va_list args; + va_start( args, pMessage ); + vsprintf_s( buffer, pMessage, args ); + va_end( args ); + + int64 nTime = GetQPCTime(); + EventWriteMark( buffer ); + return nTime; +} + +void ETWMark1F( const char *pMessage, float data1 ) +{ + EventWriteMark1F( pMessage, data1 ); +} + +void ETWMark2F( const char *pMessage, float data1, float data2 ) +{ + EventWriteMark2F( pMessage, data1, data2 ); +} + +void ETWMark3F( const char *pMessage, float data1, float data2, float data3 ) +{ + EventWriteMark3F( pMessage, data1, data2, data3 ); +} + +void ETWMark4F( const char *pMessage, float data1, float data2, float data3, float data4 ) +{ + EventWriteMark4F( pMessage, data1, data2, data3, data4 ); +} + +void ETWMark1I( const char *pMessage, int data1 ) +{ + EventWriteMark1I( pMessage, data1 ); +} + +void ETWMark2I( const char *pMessage, int data1, int data2 ) +{ + EventWriteMark2I( pMessage, data1, data2 ); +} + +void ETWMark3I( const char *pMessage, int data1, int data2, int data3 ) +{ + EventWriteMark3I( pMessage, data1, data2, data3 ); +} + +void ETWMark4I( const char *pMessage, int data1, int data2, int data3, int data4 ) +{ + EventWriteMark4I( pMessage, data1, data2, data3, data4 ); +} + +void ETWMark1S( const char *pMessage, const char* data1 ) +{ + EventWriteMark1S( pMessage, data1 ); +} + +void ETWMark2S( const char *pMessage, const char* data1, const char* data2 ) +{ + EventWriteMark2S( pMessage, data1, data2 ); +} + +// Track the depth of ETW Begin/End pairs. This needs to be per-thread +// if we start emitting marks on multiple threads. Using __declspec(thread) +// has some problems on Windows XP, but since these ETW functions only work +// on Vista+ that doesn't matter. +static __declspec( thread ) int s_nDepth; + +int64 ETWBegin( const char *pMessage ) +{ + // If we are running on Windows XP or if our providers have not been enabled + // (by xperf or other) then this will be false and we can early out. + // Be sure to check the appropriate context for the event. This is only + // worth checking if there is some cost beyond the EventWrite that we can + // avoid -- the redirectors in this file guarantee that EventWrite is always + // safe to call. + // In this case we also avoid the potentially unreliable TLS implementation + // (for dynamically loaded DLLs) on Windows XP. + if ( !VALVE_MAIN_Context.IsEnabled ) + { + return 0; + } + + int64 nTime = GetQPCTime(); + EventWriteStart( pMessage, s_nDepth++ ); + return nTime; +} + +int64 ETWEnd( const char *pMessage, int64 nStartTime ) +{ + // If we are running on Windows XP or if our providers have not been enabled + // (by xperf or other) then this will be false and we can early out. + // Be sure to check the appropriate context for the event. This is only + // worth checking if there is some cost beyond the EventWrite that we can + // avoid -- the redirectors in this file guarantee that EventWrite is always + // safe to call. + // In this case we also avoid the potentially unreliable TLS implementation + // (for dynamically loaded DLLs) on Windows XP. + if ( !VALVE_MAIN_Context.IsEnabled ) + { + return 0; + } + + int64 nTime = GetQPCTime(); + EventWriteStop( pMessage, --s_nDepth, QPCToMS( nTime - nStartTime ) ); + return nTime; +} + +static int s_nRenderFrameCount; + +int ETWGetRenderFrameNumber() +{ + return s_nRenderFrameCount; +} + +// Insert a render frame marker using the Valve-FrameRate provider. Automatically +// count the frame number and frame time. Since the frame count and elapsed time +// are tracked without paying attention to the bIsServerProcess flag the results +// will be 'unexpected' if bIsServerProcess changes value within a process. +void ETWRenderFrameMark( bool bIsServerProcess ) +{ + static int64 s_lastFrameTime; + + int64 nCurrentFrameTime = GetQPCTime(); + float flElapsedFrameTime = 0.0f; + if ( s_nRenderFrameCount ) + { + flElapsedFrameTime = QPCToMS( nCurrentFrameTime - s_lastFrameTime ); + } + + if ( bIsServerProcess ) + { + EventWriteServerRenderFrameMark( s_nRenderFrameCount, flElapsedFrameTime ); + } + else + { + EventWriteRenderFrameMark( s_nRenderFrameCount, flElapsedFrameTime ); + } + + ++s_nRenderFrameCount; + s_lastFrameTime = nCurrentFrameTime; +} + +// Insert a simulation frame marker using the Valve-FrameRate provider. Automatically +// count the frame number and frame time. Since the frame count and elapsed time +// are tracked without paying attention to the bIsServerProcess flag the results +// will be 'unexpected' if bIsServerProcess changes value within a process. +void ETWSimFrameMark( bool bIsServerProcess ) +{ + static int s_nFrameCount; + static int64 s_lastFrameTime; + + int64 nCurrentFrameTime = GetQPCTime(); + float flElapsedFrameTime = 0.0f; + if ( s_nFrameCount ) + { + flElapsedFrameTime = QPCToMS( nCurrentFrameTime - s_lastFrameTime ); + } + + if ( bIsServerProcess ) + { + EventWriteServerSimFrameMark( s_nFrameCount, flElapsedFrameTime ); + } + else + { + EventWriteSimFrameMark( s_nFrameCount, flElapsedFrameTime ); + } + + ++s_nFrameCount; + s_lastFrameTime = nCurrentFrameTime; +} + +void ETWMouseDown( int whichButton, int x, int y ) +{ + EventWriteMouse_down( whichButton, x, y ); +} + +void ETWMouseUp( int whichButton, int x, int y ) +{ + EventWriteMouse_up( whichButton, x, y ); +} + +void ETWKeyDown( int nScanCode, int nVirtualCode, const char *pChar ) +{ + EventWriteKey_down( pChar, nScanCode, nVirtualCode ); +} + +void ETWSendPacket( const char *pTo, int nWireSize, int nOutSequenceNR, int nOutSequenceNrAck ) +{ + static int s_nCumulativeWireSize; + s_nCumulativeWireSize += nWireSize; + + EventWriteSendPacket( pTo, nWireSize, nOutSequenceNR, nOutSequenceNrAck, s_nCumulativeWireSize ); +} + +void ETWThrottled() +{ + EventWriteThrottled(); +} + +void ETWReadPacket( const char *pFrom, int nWireSize, int nInSequenceNR, int nOutSequenceNRAck ) +{ + static int s_nCumulativeWireSize; + s_nCumulativeWireSize += nWireSize; + + EventWriteReadPacket( pFrom, nWireSize, nInSequenceNR, nOutSequenceNRAck, s_nCumulativeWireSize ); +} + +#endif // ETW_MARKS_ENABLED diff --git a/external/vpc/tier0/fasttimer.cpp b/external/vpc/tier0/fasttimer.cpp new file mode 100644 index 0000000..8ea732c --- /dev/null +++ b/external/vpc/tier0/fasttimer.cpp @@ -0,0 +1,23 @@ +//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "pch_tier0.h" + +#include <stdio.h> +#include "tier0/fasttimer.h" + +// NOTE: This has to be the last file included! +//#include "tier0/memdbgon.h" + +uint64 g_ClockSpeed; // Clocks/sec +unsigned long g_dwClockSpeed; +double g_ClockSpeedMicrosecondsMultiplier; +double g_ClockSpeedMillisecondsMultiplier; +double g_ClockSpeedSecondsMultiplier; + +// Constructor init the clock speed. +CClockSpeedInit g_ClockSpeedInit; diff --git a/external/vpc/tier0/logging.cpp b/external/vpc/tier0/logging.cpp new file mode 100644 index 0000000..9edf7d2 --- /dev/null +++ b/external/vpc/tier0/logging.cpp @@ -0,0 +1,698 @@ +//============ Copyright (c) Valve Corporation, All rights reserved. ============ +// +// Logging system definitions. +// +//=============================================================================== + +#include "pch_tier0.h" +#include "logging.h" + +#include <string.h> +#include "dbg.h" +#include "threadtools.h" +#include "tier0_strtools.h" // this is from tier1, but only included for inline definition of V_isspace + +#ifdef _PS3 +#include <sys/tty.h> +#endif + + +////////////////////////////////////////////////////////////////////////// +// Define commonly used channels here +////////////////////////////////////////////////////////////////////////// +DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_GENERAL, "General" ); + +DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_ASSERT, "Assert" ); + +// Corresponds to ConMsg/ConWarning/etc. with a level <= 1. +// Only errors are spewed by default. +BEGIN_DEFINE_LOGGING_CHANNEL( LOG_CONSOLE, "Console", LCF_CONSOLE_ONLY, LS_ERROR ); +ADD_LOGGING_CHANNEL_TAG( "Console" ); +END_DEFINE_LOGGING_CHANNEL(); + +// Corresponds to DevMsg/DevWarning/etc. with a level <= 1. +// Only errors are spewed by default. +BEGIN_DEFINE_LOGGING_CHANNEL( LOG_DEVELOPER, "Developer", LCF_CONSOLE_ONLY, LS_ERROR ); +ADD_LOGGING_CHANNEL_TAG( "Developer" ); +END_DEFINE_LOGGING_CHANNEL(); + +// Corresponds to ConMsg/ConWarning/etc. with a level >= 2. +// Only errors are spewed by default. +BEGIN_DEFINE_LOGGING_CHANNEL( LOG_DEVELOPER_CONSOLE, "DeveloperConsole", LCF_CONSOLE_ONLY, LS_ERROR ); +ADD_LOGGING_CHANNEL_TAG( "DeveloperVerbose" ); +ADD_LOGGING_CHANNEL_TAG( "Console" ); +END_DEFINE_LOGGING_CHANNEL(); + +// Corresponds to DevMsg/DevWarning/etc, with a level >= 2. +// Only errors are spewed by default. +BEGIN_DEFINE_LOGGING_CHANNEL( LOG_DEVELOPER_VERBOSE, "DeveloperVerbose", LCF_CONSOLE_ONLY, LS_ERROR, Color( 192, 128, 192, 255 ) ); +ADD_LOGGING_CHANNEL_TAG( "DeveloperVerbose" ); +END_DEFINE_LOGGING_CHANNEL(); + +////////////////////////////////////////////////////////////////////////// +// Globals +////////////////////////////////////////////////////////////////////////// + +// The index of the logging state used by the current thread. This defaults to 0 across all threads, +// which indicates that the global listener set should be used (CLoggingSystem::m_nGlobalStateIndex). +// +// NOTE: +// Because our linux TLS implementation does not support embedding a thread local +// integer in a class, the logging system must use a global thread-local integer. +// This means that we can only have one instance of CLoggingSystem, although +// we could support additional instances if we are willing to lose support for +// thread-local spew handling. +// There is no other reason why this class must be a singleton, except +// for the fact that there's no reason to have more than one in existence. +bool g_bEnforceLoggingSystemSingleton = false; + +#ifdef _PS3 +#include "tls_ps3.h" +#else // _PS3 +CTHREADLOCALINT g_nThreadLocalStateIndex; +#endif // _PS3 + +////////////////////////////////////////////////////////////////////////// +// Implementation +////////////////////////////////////////////////////////////////////////// + +CLoggingSystem *g_pGlobalLoggingSystem = NULL; + +// This function does not get inlined due to the static variable :( +CLoggingSystem *GetGlobalLoggingSystem_Internal() +{ + static CLoggingSystem globalLoggingSystem; + g_pGlobalLoggingSystem = &globalLoggingSystem; + return &globalLoggingSystem; +} + +// This function can get inlined +CLoggingSystem *GetGlobalLoggingSystem() +{ + return ( g_pGlobalLoggingSystem == NULL ) ? GetGlobalLoggingSystem_Internal() : g_pGlobalLoggingSystem; +} + +CLoggingSystem::CLoggingSystem() : +m_nChannelCount( 0 ), +m_nChannelTagCount( 0 ), +m_nTagNamePoolIndex( 0 ), +m_nGlobalStateIndex( 0 ) +{ + Assert( !g_bEnforceLoggingSystemSingleton ); + g_bEnforceLoggingSystemSingleton = true; +#if !defined( _PS3 ) && !defined(POSIX) && !defined(PLATFORM_WINDOWS) + // Due to uncertain constructor ordering (g_nThreadLocalStateIndex + // may not be constructed yet so TLS index may not be available yet) + // we cannot initialize the state index here without risking + // AppVerifier errors and undefined behavior. Luckily TlsAlloc values + // are guaranteed to be zero-initialized so we don't need to zero-init, + // this, and in fact we can't for all threads. + // TLS on PS3 is zero-initialized in global ELF section + // TLS is also not accessible at this point before PRX entry point runs + g_nThreadLocalStateIndex = 0; +#endif + + m_LoggingStates[0].m_nPreviousStackEntry = -1; + + m_LoggingStates[0].m_nListenerCount = 1; + m_LoggingStates[0].m_RegisteredListeners[0] = &m_DefaultLoggingListener; + m_LoggingStates[0].m_pLoggingResponse = &m_DefaultLoggingResponse; + + // Mark all other logging state blocks as unused. + for ( int i = 1; i < MAX_LOGGING_STATE_COUNT; ++ i ) + { + m_LoggingStates[i].m_nListenerCount = -1; + } + + m_pStateMutex = NULL; +} + +CLoggingSystem::~CLoggingSystem() +{ + g_bEnforceLoggingSystemSingleton = false; + delete m_pStateMutex; +} + +LoggingChannelID_t CLoggingSystem::RegisterLoggingChannel( const char *pChannelName, RegisterTagsFunc registerTagsFunc, int flags, LoggingSeverity_t severity, Color spewColor ) +{ + if ( m_nChannelCount >= MAX_LOGGING_CHANNEL_COUNT ) + { + // Out of logging channels... catastrophic fail! + Log_Error( LOG_GENERAL, "Out of logging channels.\n" ); + Assert( 0 ); + return INVALID_LOGGING_CHANNEL_ID; + } + else + { + // Channels can be multiply defined, in which case return the ID of the existing channel. + for ( int i = 0; i < m_nChannelCount; ++ i ) + { + if ( V_tier0_stricmp( m_RegisteredChannels[i].m_Name, pChannelName ) == 0 ) + { + // OK to call the tag registration callback; duplicates will be culled away. + // This allows multiple people to register a logging channel, and the union of all tags will be registered. + if ( registerTagsFunc != NULL ) + { + registerTagsFunc(); + } + + // If a logging channel is registered multiple times, only one of the registrations should specify flags/severity/color. + if ( m_RegisteredChannels[i].m_Flags == 0 && m_RegisteredChannels[i].m_MinimumSeverity == LS_MESSAGE && m_RegisteredChannels[i].m_SpewColor == UNSPECIFIED_LOGGING_COLOR ) + { + m_RegisteredChannels[i].m_Flags = ( LoggingChannelFlags_t )flags; + m_RegisteredChannels[i].m_MinimumSeverity = severity; + m_RegisteredChannels[i].m_SpewColor = spewColor; + } + else + { + AssertMsg( flags == 0 || flags == m_RegisteredChannels[i].m_Flags, "Non-zero or mismatched flags specified in logging channel re-registration!" ); + AssertMsg( severity == LS_MESSAGE || severity == m_RegisteredChannels[i].m_MinimumSeverity, "Non-default or mismatched severity specified in logging channel re-registration!" ); + AssertMsg( spewColor == UNSPECIFIED_LOGGING_COLOR || spewColor == m_RegisteredChannels[i].m_SpewColor, "Non-default or mismatched color specified in logging channel re-registration!" ); + } + + return m_RegisteredChannels[i].m_ID; + } + } + + m_RegisteredChannels[m_nChannelCount].m_ID = m_nChannelCount; + m_RegisteredChannels[m_nChannelCount].m_Flags = ( LoggingChannelFlags_t )flags; + m_RegisteredChannels[m_nChannelCount].m_MinimumSeverity = severity; + m_RegisteredChannels[m_nChannelCount].m_SpewColor = spewColor; + strncpy( m_RegisteredChannels[m_nChannelCount].m_Name, pChannelName, MAX_LOGGING_IDENTIFIER_LENGTH ); + + if ( registerTagsFunc != NULL ) + { + registerTagsFunc(); + } + return m_nChannelCount ++; + } +} + +LoggingChannelID_t CLoggingSystem::FindChannel( const char *pChannelName ) const +{ + for ( int i = 0; i < m_nChannelCount; ++ i ) + { + if ( V_tier0_stricmp( m_RegisteredChannels[i].m_Name, pChannelName ) == 0 ) + { + return i; + } + } + + return INVALID_LOGGING_CHANNEL_ID; +} + +void CLoggingSystem::AddTagToCurrentChannel( const char *pTagName ) +{ + // Add tags at the head of the tag-list of the most recently added channel. + LoggingChannel_t *pChannel = &m_RegisteredChannels[m_nChannelCount]; + + // First check for duplicates + if ( pChannel->HasTag( pTagName ) ) + { + return; + } + + LoggingTag_t *pTag = AllocTag( pTagName ); + + pTag->m_pNextTag = pChannel->m_pFirstTag; + pChannel->m_pFirstTag = pTag; +} + +void CLoggingSystem::SetChannelSpewLevel( LoggingChannelID_t channelID, LoggingSeverity_t minimumSeverity ) +{ + GetChannel( channelID )->SetSpewLevel( minimumSeverity ); +} + +void CLoggingSystem::SetChannelSpewLevelByName( const char *pName, LoggingSeverity_t minimumSeverity ) +{ + for ( int i = 0; i < m_nChannelCount; ++ i ) + { + if ( V_tier0_stricmp( m_RegisteredChannels[i].m_Name, pName ) == 0 ) + { + m_RegisteredChannels[i].SetSpewLevel( minimumSeverity ); + } + } +} + +void CLoggingSystem::SetChannelSpewLevelByTag( const char *pTag, LoggingSeverity_t minimumSeverity ) +{ + for ( int i = 0; i < m_nChannelCount; ++ i ) + { + if ( m_RegisteredChannels[i].HasTag( pTag ) ) + { + m_RegisteredChannels[i].SetSpewLevel( minimumSeverity ); + } + } +} + +void CLoggingSystem::PushLoggingState( bool bThreadLocal, bool bClearState ) +{ + if ( !m_pStateMutex ) + m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + + int nNewState = FindUnusedStateIndex(); + // Ensure we're not out of state blocks. + Assert( nNewState != -1 ); + + int nCurrentState = bThreadLocal ? (int)g_nThreadLocalStateIndex : m_nGlobalStateIndex; + + if ( bClearState ) + { + m_LoggingStates[nNewState].m_nListenerCount = 0; + m_LoggingStates[nNewState].m_pLoggingResponse = &m_DefaultLoggingResponse; + } + else + { + m_LoggingStates[nNewState] = m_LoggingStates[nCurrentState]; + } + + m_LoggingStates[nNewState].m_nPreviousStackEntry = nCurrentState; + + if ( bThreadLocal ) + { + g_nThreadLocalStateIndex = nNewState; + } + else + { + m_nGlobalStateIndex = nNewState; + } + + m_pStateMutex->Unlock(); +} + +void CLoggingSystem::PopLoggingState( bool bThreadLocal ) +{ + if ( !m_pStateMutex ) + m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + + int nCurrentState = bThreadLocal ? (int)g_nThreadLocalStateIndex : m_nGlobalStateIndex; + + // Shouldn't be less than 0 (implies error during Push()) or 0 (implies that Push() was never called) + Assert( nCurrentState > 0 ); + + // Mark the current state as unused. + m_LoggingStates[nCurrentState].m_nListenerCount = -1; + + if ( bThreadLocal ) + { + g_nThreadLocalStateIndex = m_LoggingStates[nCurrentState].m_nPreviousStackEntry; + } + else + { + m_nGlobalStateIndex = m_LoggingStates[nCurrentState].m_nPreviousStackEntry; + } + + m_pStateMutex->Unlock(); +} + +void CLoggingSystem::RegisterLoggingListener( ILoggingListener *pListener ) +{ + if ( !m_pStateMutex ) + m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + LoggingState_t *pState = GetCurrentState(); + if ( pState->m_nListenerCount > MAX_LOGGING_CHANNEL_COUNT ) + { + // Out of logging listener slots... catastrophic fail! + Assert( 0 ); + } + else + { + pState->m_RegisteredListeners[pState->m_nListenerCount] = pListener; + ++ pState->m_nListenerCount; + } + m_pStateMutex->Unlock(); +} + +void CLoggingSystem::UnregisterLoggingListener( ILoggingListener *pListener ) +{ + if ( !m_pStateMutex ) + m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + LoggingState_t *pState = GetCurrentState(); + for ( int i = 0; i < pState->m_nListenerCount; ++ i ) + { + if ( pState->m_RegisteredListeners[i] == pListener ) + { + // Shuffle all the listeners ahead over these, and reduce the count. + for ( int j = i; j < (pState->m_nListenerCount-1); ++ j ) + { + pState->m_RegisteredListeners[j] = pState->m_RegisteredListeners[j+1]; + } + pState->m_nListenerCount--; + break; + } + } + m_pStateMutex->Unlock(); +} + +bool CLoggingSystem::IsListenerRegistered( ILoggingListener *pListener ) +{ + if ( !m_pStateMutex ) + m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + const LoggingState_t *pState = GetCurrentState(); + bool bFound = false; + for ( int i = 0; i < pState->m_nListenerCount; ++ i ) + { + if ( pState->m_RegisteredListeners[i] == pListener ) + { + bFound = true; + break; + } + } + m_pStateMutex->Unlock(); + return bFound; +} + +void CLoggingSystem::ResetCurrentLoggingState() +{ + if ( !m_pStateMutex ) + m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + LoggingState_t *pState = GetCurrentState(); + pState->m_nListenerCount = 0; + pState->m_pLoggingResponse = &m_DefaultLoggingResponse; + m_pStateMutex->Unlock(); +} + +void CLoggingSystem::SetLoggingResponsePolicy( ILoggingResponsePolicy *pLoggingResponse ) +{ + if ( !m_pStateMutex ) + m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + LoggingState_t *pState = GetCurrentState(); + if ( pLoggingResponse == NULL ) + { + pState->m_pLoggingResponse = &m_DefaultLoggingResponse; + } + else + { + pState->m_pLoggingResponse = pLoggingResponse; + } + m_pStateMutex->Unlock(); +} + +LoggingResponse_t CLoggingSystem::LogDirect( LoggingChannelID_t channelID, LoggingSeverity_t severity, Color color, const tchar *pMessage ) +{ + Assert( IsValidChannelID( channelID ) ); + if ( !IsValidChannelID( channelID ) ) + return LR_CONTINUE; + + LoggingContext_t context; + context.m_ChannelID = channelID; + context.m_Flags = m_RegisteredChannels[channelID].m_Flags; + context.m_Severity = severity; + context.m_Color = ( color == UNSPECIFIED_LOGGING_COLOR ) ? m_RegisteredChannels[channelID].m_SpewColor : color; + + // It is assumed that the mutex is reentrant safe on all platforms. + if ( !m_pStateMutex ) + m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + + LoggingState_t *pState = GetCurrentState(); + + for ( int i = 0; i < pState->m_nListenerCount; ++ i ) + { + pState->m_RegisteredListeners[i]->Log( &context, pMessage ); + } + +#if defined( _PS3 ) && !defined( _CERT ) + if ( !pState->m_nListenerCount ) + { + unsigned int unBytesWritten; + sys_tty_write( SYS_TTYP15, pMessage, strlen( pMessage ), &unBytesWritten ); + } +#endif + + LoggingResponse_t response = pState->m_pLoggingResponse->OnLog( &context ); + + m_pStateMutex->Unlock(); + + switch( response ) + { + case LR_DEBUGGER: + // Asserts put the debug break in the macro itself so the code breaks at the failure point. + if ( severity != LS_ASSERT ) + { + DebuggerBreakIfDebugging(); + } + break; + + case LR_ABORT: + Log_Msg( LOG_DEVELOPER_VERBOSE, "Exiting due to logging LR_ABORT request.\n" ); + Plat_ExitProcess( EXIT_FAILURE ); + break; + } + + return response; +} + +CLoggingSystem::LoggingChannel_t *CLoggingSystem::GetChannel( LoggingChannelID_t channelID ) +{ + Assert( IsValidChannelID( channelID ) ); + return &m_RegisteredChannels[channelID]; +} + +const CLoggingSystem::LoggingChannel_t *CLoggingSystem::GetChannel( LoggingChannelID_t channelID ) const +{ + Assert( IsValidChannelID( channelID ) ); + return &m_RegisteredChannels[channelID]; +} + +CLoggingSystem::LoggingState_t *CLoggingSystem::GetCurrentState() +{ + // Assume the caller grabbed the mutex. + int nState = g_nThreadLocalStateIndex; + if ( nState != 0 ) + { + Assert( nState > 0 && nState < MAX_LOGGING_STATE_COUNT ); + return &m_LoggingStates[nState]; + } + else + { + Assert( m_nGlobalStateIndex >= 0 && m_nGlobalStateIndex < MAX_LOGGING_STATE_COUNT ); + return &m_LoggingStates[m_nGlobalStateIndex]; + } +} + +const CLoggingSystem::LoggingState_t *CLoggingSystem::GetCurrentState() const +{ + // Assume the caller grabbed the mutex. + int nState = g_nThreadLocalStateIndex; + if ( nState != 0 ) + { + Assert( nState > 0 && nState < MAX_LOGGING_STATE_COUNT ); + return &m_LoggingStates[nState]; + } + else + { + Assert( m_nGlobalStateIndex >= 0 && m_nGlobalStateIndex < MAX_LOGGING_STATE_COUNT ); + return &m_LoggingStates[m_nGlobalStateIndex]; + } +} + +int CLoggingSystem::FindUnusedStateIndex() +{ + for ( int i = 0; i < MAX_LOGGING_STATE_COUNT; ++ i ) + { + if ( m_LoggingStates[i].m_nListenerCount < 0 ) + { + return i; + } + } + return -1; +} + +CLoggingSystem::LoggingTag_t *CLoggingSystem::AllocTag( const char *pTagName ) +{ + Assert( m_nChannelTagCount < MAX_LOGGING_TAG_COUNT ); + LoggingTag_t *pTag = &m_ChannelTags[m_nChannelTagCount ++]; + + pTag->m_pNextTag = NULL; + pTag->m_pTagName = m_TagNamePool + m_nTagNamePoolIndex; + + // Copy string into pool. + size_t nTagLength = strlen( pTagName ); + Assert( m_nTagNamePoolIndex + nTagLength + 1 <= MAX_LOGGING_TAG_CHARACTER_COUNT ); + strcpy( m_TagNamePool + m_nTagNamePoolIndex, pTagName ); + m_nTagNamePoolIndex += ( int )nTagLength + 1; + + return pTag; +} + +LoggingChannelID_t LoggingSystem_RegisterLoggingChannel( const char *pName, RegisterTagsFunc registerTagsFunc, int flags, LoggingSeverity_t severity, Color color ) +{ + return GetGlobalLoggingSystem()->RegisterLoggingChannel( pName, registerTagsFunc, flags, severity, color ); +} + +void LoggingSystem_ResetCurrentLoggingState() +{ + GetGlobalLoggingSystem()->ResetCurrentLoggingState(); +} + +void LoggingSystem_RegisterLoggingListener( ILoggingListener *pListener ) +{ + GetGlobalLoggingSystem()->RegisterLoggingListener( pListener ); +} + +void LoggingSystem_UnregisterLoggingListener( ILoggingListener *pListener ) +{ + GetGlobalLoggingSystem()->UnregisterLoggingListener( pListener ); +} + +void LoggingSystem_SetLoggingResponsePolicy( ILoggingResponsePolicy *pResponsePolicy ) +{ + GetGlobalLoggingSystem()->SetLoggingResponsePolicy( pResponsePolicy ); +} + +void LoggingSystem_PushLoggingState( bool bThreadLocal, bool bClearState ) +{ + GetGlobalLoggingSystem()->PushLoggingState( bThreadLocal, bClearState ); +} + +void LoggingSystem_PopLoggingState( bool bThreadLocal ) +{ + GetGlobalLoggingSystem()->PopLoggingState( bThreadLocal ); +} + +void LoggingSystem_AddTagToCurrentChannel( const char *pTagName ) +{ + GetGlobalLoggingSystem()->AddTagToCurrentChannel( pTagName ); +} + +LoggingChannelID_t LoggingSystem_FindChannel( const char *pChannelName ) +{ + return GetGlobalLoggingSystem()->FindChannel( pChannelName ); +} + +int LoggingSystem_GetChannelCount() +{ + return GetGlobalLoggingSystem()->GetChannelCount(); +} + +LoggingChannelID_t LoggingSystem_GetFirstChannelID() +{ + return ( GetGlobalLoggingSystem()->GetChannelCount() > 0 ) ? 0 : INVALID_LOGGING_CHANNEL_ID; +} + +LoggingChannelID_t LoggingSystem_GetNextChannelID( LoggingChannelID_t channelID ) +{ + int nChannelCount = GetGlobalLoggingSystem()->GetChannelCount(); + int nNextChannel = channelID + 1; + return ( nNextChannel < nChannelCount ) ? nNextChannel : INVALID_LOGGING_CHANNEL_ID; +} + +const CLoggingSystem::LoggingChannel_t *LoggingSystem_GetChannel( LoggingChannelID_t channelIndex ) +{ + return GetGlobalLoggingSystem()->GetChannel( channelIndex ); +} + +bool LoggingSystem_HasTag( LoggingChannelID_t channelID, const char *pTag ) +{ + return GetGlobalLoggingSystem()->HasTag( channelID, pTag ); +} + +bool LoggingSystem_IsChannelEnabled( LoggingChannelID_t channelID, LoggingSeverity_t severity ) +{ + return GetGlobalLoggingSystem()->IsChannelEnabled( channelID, severity ); +} + +void LoggingSystem_SetChannelSpewLevel( LoggingChannelID_t channelID, LoggingSeverity_t minimumSeverity ) +{ + GetGlobalLoggingSystem()->SetChannelSpewLevel( channelID, minimumSeverity ); +} + +void LoggingSystem_SetChannelSpewLevelByName( const char *pName, LoggingSeverity_t minimumSeverity ) +{ + GetGlobalLoggingSystem()->SetChannelSpewLevelByName( pName, minimumSeverity ); +} + +void LoggingSystem_SetChannelSpewLevelByTag( const char *pTag, LoggingSeverity_t minimumSeverity ) +{ + GetGlobalLoggingSystem()->SetChannelSpewLevelByTag( pTag, minimumSeverity ); +} + +int32 LoggingSystem_GetChannelColor( LoggingChannelID_t channelID ) +{ + return GetGlobalLoggingSystem()->GetChannelColor( channelID ).GetRawColor(); +} + +void LoggingSystem_SetChannelColor( LoggingChannelID_t channelID, int color ) +{ + Color c; + c.SetRawColor( color ); + GetGlobalLoggingSystem()->SetChannelColor( channelID, c ); +} + +LoggingChannelFlags_t LoggingSystem_GetChannelFlags( LoggingChannelID_t channelID ) +{ + return GetGlobalLoggingSystem()->GetChannelFlags( channelID ); +} + +void LoggingSystem_SetChannelFlags( LoggingChannelID_t channelID, LoggingChannelFlags_t flags ) +{ + GetGlobalLoggingSystem()->SetChannelFlags( channelID, flags ); +} + +LoggingResponse_t LoggingSystem_Log( LoggingChannelID_t channelID, LoggingSeverity_t severity, const char *pMessageFormat, ... ) +{ + if ( !GetGlobalLoggingSystem()->IsChannelEnabled( channelID, severity ) ) + return LR_CONTINUE; + + tchar formattedMessage[MAX_LOGGING_MESSAGE_LENGTH]; + + va_list args; + va_start( args, pMessageFormat ); + Tier0Internal_vsntprintf( formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, pMessageFormat, args ); + va_end( args ); + + return GetGlobalLoggingSystem()->LogDirect( channelID, severity, UNSPECIFIED_LOGGING_COLOR, formattedMessage ); +} + +LoggingResponse_t LoggingSystem_Log( LoggingChannelID_t channelID, LoggingSeverity_t severity, Color spewColor, const char *pMessageFormat, ... ) +{ + if ( !GetGlobalLoggingSystem()->IsChannelEnabled( channelID, severity ) ) + return LR_CONTINUE; + + tchar formattedMessage[MAX_LOGGING_MESSAGE_LENGTH]; + + va_list args; + va_start( args, pMessageFormat ); + Tier0Internal_vsntprintf( formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, pMessageFormat, args ); + va_end( args ); + + return GetGlobalLoggingSystem()->LogDirect( channelID, severity, spewColor, formattedMessage ); +} + +LoggingResponse_t LoggingSystem_LogDirect( LoggingChannelID_t channelID, LoggingSeverity_t severity, Color spewColor, const char *pMessage ) +{ + if ( !GetGlobalLoggingSystem()->IsChannelEnabled( channelID, severity ) ) + return LR_CONTINUE; + return GetGlobalLoggingSystem()->LogDirect( channelID, severity, spewColor, pMessage ); +} + +LoggingResponse_t LoggingSystem_LogAssert( const char *pMessageFormat, ... ) +{ + if ( !GetGlobalLoggingSystem()->IsChannelEnabled( LOG_ASSERT, LS_ASSERT ) ) + return LR_CONTINUE; + + tchar formattedMessage[MAX_LOGGING_MESSAGE_LENGTH]; + + va_list args; + va_start( args, pMessageFormat ); + Tier0Internal_vsntprintf( formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, pMessageFormat, args ); + va_end( args ); + + return GetGlobalLoggingSystem()->LogDirect( LOG_ASSERT, LS_ASSERT, UNSPECIFIED_LOGGING_COLOR, formattedMessage ); +} diff --git a/external/vpc/tier0/mem.cpp b/external/vpc/tier0/mem.cpp new file mode 100644 index 0000000..96468c3 --- /dev/null +++ b/external/vpc/tier0/mem.cpp @@ -0,0 +1,79 @@ +//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Memory allocation! +// +// $NoKeywords: $ +//=============================================================================// + +#include "pch_tier0.h" +#include "tier0/mem.h" +//#include <malloc.h> +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifndef STEAM +#define PvRealloc realloc +#define PvAlloc malloc +#define PvExpand _expand +#endif + +enum +{ + MAX_STACK_DEPTH = 32 +}; + +static uint8 *s_pBuf = NULL; +static int s_pBufStackDepth[MAX_STACK_DEPTH]; +static int s_nBufDepth = -1; +static int s_nBufCurSize = 0; +static int s_nBufAllocSize = 0; + +//----------------------------------------------------------------------------- +// Other DLL-exported methods for particular kinds of memory +//----------------------------------------------------------------------------- +void *MemAllocScratch( int nMemSize ) +{ + // Minimally allocate 1M scratch + if (s_nBufAllocSize < s_nBufCurSize + nMemSize) + { + s_nBufAllocSize = s_nBufCurSize + nMemSize; + if (s_nBufAllocSize < 2 * 1024) + { + s_nBufAllocSize = 2 * 1024; + } + + if (s_pBuf) + { + s_pBuf = (uint8*)PvRealloc( s_pBuf, s_nBufAllocSize ); + Assert( s_pBuf ); + } + else + { + s_pBuf = (uint8*)PvAlloc( s_nBufAllocSize ); + } + } + + int nBase = s_nBufCurSize; + s_nBufCurSize += nMemSize; + ++s_nBufDepth; + Assert( s_nBufDepth < MAX_STACK_DEPTH ); + s_pBufStackDepth[s_nBufDepth] = nMemSize; + + return &s_pBuf[nBase]; +} + +void MemFreeScratch() +{ + Assert( s_nBufDepth >= 0 ); + s_nBufCurSize -= s_pBufStackDepth[s_nBufDepth]; + --s_nBufDepth; +} + +#ifdef POSIX +void ZeroMemory( void *mem, size_t length ) +{ + memset( mem, 0x0, length ); +} +#endif diff --git a/external/vpc/tier0/mem_helpers.cpp b/external/vpc/tier0/mem_helpers.cpp new file mode 100644 index 0000000..52f2003 --- /dev/null +++ b/external/vpc/tier0/mem_helpers.cpp @@ -0,0 +1,186 @@ +//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#include "tier0/platform.h" +#include "tier0/icommandline.h" +#include "tier0/dbg.h" +#include "mem_helpers.h" +#include <string.h> +//#include <malloc.h> + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +// Needed for debugging +const char *g_pszModule = "tier0"; +bool g_bInitMemory = true; + +#if defined(PLATFORM_POSIX) || defined( PLATFORM_PS3) +void DoApplyMemoryInitializations( void *pMem, size_t nSize ) +{ +} + +size_t CalcHeapUsed() +{ + return 0; +} +#else + +unsigned long g_dwFeeFee = 0xffeeffee; + +// Generated by Mathematica. +unsigned char g_RandomValues[256] = { + 95, 126, 220, 71, 92, 179, 95, 219, 111, 150, 38, 155, 181, 62, 40, 231, 238, + 54, 47, 55, 186, 204, 64, 70, 118, 94, 107, 251, 199, 140, 67, 87, 86, 127, + 210, 41, 21, 90, 208, 24, 167, 204, 32, 254, 38, 51, 9, 11, 38, 33, 188, 104, + 0, 75, 119, 24, 122, 203, 24, 164, 250, 224, 241, 182, 213, 201, 173, 67, + 200, 255, 244, 227, 46, 219, 26, 149, 218, 132, 120, 154, 227, 244, 106, 198, + 109, 87, 150, 40, 16, 99, 169, 193, 100, 156, 78, 171, 246, 47, 84, 119, 10, + 52, 207, 171, 230, 90, 90, 127, 180, 153, 68, 140, 62, 14, 87, 57, 208, 154, + 116, 29, 131, 177, 224, 187, 51, 148, 142, 245, 152, 230, 184, 117, 91, 146, + 235, 153, 35, 104, 187, 177, 215, 131, 17, 49, 211, 244, 60, 152, 103, 248, + 51, 224, 237, 240, 51, 30, 10, 233, 253, 106, 252, 73, 134, 136, 178, 86, + 228, 107, 77, 255, 85, 242, 204, 119, 102, 53, 209, 35, 123, 32, 252, 210, + 43, 12, 136, 167, 155, 210, 71, 254, 178, 172, 3, 230, 93, 208, 196, 68, 235, + 16, 106, 189, 201, 177, 85, 78, 206, 187, 48, 68, 64, 190, 117, 236, 49, 174, + 105, 63, 207, 70, 170, 93, 6, 110, 52, 111, 169, 92, 247, 86, 10, 174, 207, + 240, 104, 209, 81, 177, 123, 189, 175, 212, 101, 219, 114, 243, 44, 91, 51, + 139, 91, 57, 120, 41, 98, 119 }; + +unsigned long g_iCurRandomValueOffset = 0; + + +void InitializeToFeeFee( void *pMem, size_t nSize ) +{ + unsigned long *pCurDWord = (unsigned long*)pMem; + size_t nDWords = nSize >> 2; + while ( nDWords ) + { + *pCurDWord = 0xffeeffee; + ++pCurDWord; + --nDWords; + } + + unsigned char *pCurChar = (unsigned char*)pCurDWord; + size_t nBytes = nSize & 3; + size_t iOffset = 0; + while ( nBytes ) + { + *pCurChar = ((unsigned char*)&g_dwFeeFee)[iOffset]; + ++iOffset; + --nBytes; + ++pCurChar; + } +} + + +void InitializeToRandom( void *pMem, size_t nSize ) +{ + unsigned char *pOut = (unsigned char *)pMem; + for ( size_t i=0; i < nSize; i++ ) + { + pOut[i] = g_RandomValues[(g_iCurRandomValueOffset & 255)]; + ++g_iCurRandomValueOffset; + } +} + + +void DoApplyMemoryInitializations( void *pMem, size_t nSize ) +{ + if ( !pMem ) + return; + + // If they passed -noinitmemory on the command line, don't do anything here. + Assert( g_bInitMemory ); + + // First time we get in here, remember all the settings. + static bool bDebuggerPresent = Plat_IsInDebugSession(); + static bool bCheckedCommandLine = false; + static bool bRandomizeMemory = false; + if ( !bCheckedCommandLine ) + { + bCheckedCommandLine = true; + + //APS + char *pStr = (char*)Plat_GetCommandLineA(); + if ( pStr ) + { + char tempStr[512]; + strncpy( tempStr, pStr, sizeof( tempStr ) - 1 ); + tempStr[ sizeof( tempStr ) - 1 ] = 0; + _strupr( tempStr ); + + if ( strstr( tempStr, "-RANDOMIZEMEMORY" ) ) + bRandomizeMemory = true; + + if ( strstr( tempStr, "-NOINITMEMORY" ) ) + g_bInitMemory = false; + } + } + + if ( bRandomizeMemory ) + { + // They asked for it.. randomize all the memory. + InitializeToRandom( pMem, nSize ); + } + else + { + if ( bDebuggerPresent ) + { + // Ok, it's already set to 0xbaadf00d, but we want something that will make floating-point #'s NANs. + InitializeToFeeFee( pMem, nSize ); + } + else + { +#if defined(_DEBUG) || defined(USE_LIGHT_MEM_DEBUG) +#ifdef LIGHT_MEM_DEBUG_REQUIRES_CMD_LINE_SWITCH + extern bool g_bUsingLMD; + if ( !g_bUsingLMD ) + { + return; + } +#endif + // Ok, it's already set to 0xcdcdcdcd, but we want something that will make floating-point #'s NANs. + InitializeToFeeFee( pMem, nSize ); +#endif + } + } +} + +size_t CalcHeapUsed() +{ +#if defined( _X360 ) + return 0; +#else + _HEAPINFO hinfo; + int heapstatus; + intp nTotal; + + nTotal = 0; + hinfo._pentry = NULL; + while( ( heapstatus = _heapwalk( &hinfo ) ) == _HEAPOK ) + { + nTotal += (hinfo._useflag == _USEDENTRY) ? hinfo._size : 0; + } + + switch (heapstatus) + { + case _HEAPEMPTY: + case _HEAPEND: + // success + break; + + default: + // heap corrupted + nTotal = -1; + } + + return nTotal; +#endif +} + +#endif // not PLATFORM_POSIX + diff --git a/external/vpc/tier0/mem_helpers.h b/external/vpc/tier0/mem_helpers.h new file mode 100644 index 0000000..cdf2bfc --- /dev/null +++ b/external/vpc/tier0/mem_helpers.h @@ -0,0 +1,33 @@ +//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +//===========================================================================// + +#ifndef MEM_HELPERS_H +#define MEM_HELPERS_H +#ifdef _WIN32 +#pragma once +#endif + + +// Normally, the runtime libraries like to mess with the memory returned by malloc(), +// which can create problems trying to repro bugs in debug builds or in the debugger. +// +// If the debugger is present, it initializes data to 0xbaadf00d, which makes floating +// point numbers come out to about 0.1. +// +// If the debugger is not present, and it's a debug build, then you get 0xcdcdcdcd, +// which is about 25 million. +// +// Otherwise, you get uninitialized memory. +// +// In here, we make sure the memory is either random garbage, or it's set to +// 0xffeeffee, which casts to a NAN. +extern bool g_bInitMemory; +#define ApplyMemoryInitializations( pMem, nSize ) if ( !g_bInitMemory ) ; else { DoApplyMemoryInitializations( pMem, nSize ); } +void DoApplyMemoryInitializations( void *pMem, size_t nSize ); + +size_t CalcHeapUsed(); + +#endif // MEM_HELPERS_H diff --git a/external/vpc/tier0/mem_impl_type.h b/external/vpc/tier0/mem_impl_type.h new file mode 100644 index 0000000..f01b653 --- /dev/null +++ b/external/vpc/tier0/mem_impl_type.h @@ -0,0 +1,6 @@ + +#if ( (!defined( POSIX )||defined(_GAMECONSOLE)) && (defined(_DEBUG) || defined(USE_MEM_DEBUG) ) ) +#define MEM_IMPL_TYPE_DBG 1 +#else +#define MEM_IMPL_TYPE_STD 1 +#endif diff --git a/external/vpc/tier0/memdbg.cpp b/external/vpc/tier0/memdbg.cpp new file mode 100644 index 0000000..ad9c153 --- /dev/null +++ b/external/vpc/tier0/memdbg.cpp @@ -0,0 +1,2762 @@ +//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: Memory allocation! +// +// $NoKeywords: $ +//===========================================================================// + + +#include "pch_tier0.h" + +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + +//#include <malloc.h> +#include <string.h> +#include "tier0/dbg.h" +#include "tier0/stackstats.h" +#include "tier0/memalloc.h" +#include "tier0/fasttimer.h" +#include "mem_helpers.h" +#ifdef PLATFORM_WINDOWS_PC +#undef WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <crtdbg.h> +#endif +#ifdef OSX +#include <malloc/malloc.h> +#include <stdlib.h> +#endif + +#include <map> +#include <set> +#include <limits.h> +#include "tier0/threadtools.h" +#ifdef _X360 +#include "xbox/xbox_console.h" +#endif + +#ifdef _PS3 +#include "sys/memory.h" +#include "tls_ps3.h" +#include "ps3/ps3_helpers.h" +#include "memoverride_ps3.h" +#endif + +#ifdef USE_LIGHT_MEM_DEBUG +#undef USE_MEM_DEBUG +#endif + +#if (!defined( POSIX ) && (defined(_DEBUG) || defined(USE_MEM_DEBUG))) +#pragma message ("USE_MEM_DEBUG is enabled in a release build. Don't check this in!") +#endif + +#include "mem_impl_type.h" + +#if MEM_IMPL_TYPE_DBG + +#if defined(_WIN32) && ( !defined(_X360) && !defined(_WIN64) ) +//be sure to disable frame pointer omission for all projects. "vpc /nofpo" when using stack traces +//#define USE_STACK_TRACES +// or: +//#define USE_STACK_TRACES_DETAILED +const size_t STACK_TRACE_LENGTH = 32; +#endif + +//prevent stupid bugs from checking one and not the other +#if defined( USE_STACK_TRACES_DETAILED ) && !defined( USE_STACK_TRACES ) +#define USE_STACK_TRACES //don't comment me. I'm a safety check +#endif + +#if defined( USE_STACK_TRACES ) +#define SORT_STACK_TRACE_DESCRIPTION_DUMPS +#endif + +#if (defined( USE_STACK_TRACES )) && !(defined( TIER0_FPO_DISABLED ) || defined( _DEBUG )) +#error Stack traces will not work unless FPO is disabled for every function traced through. Rebuild everything with FPO disabled "vpc /nofpo" +#endif + +//----------------------------------------------------------------------------- + +#ifdef _PS3 +MemOverrideRawCrtFunctions_t *g_pMemOverrideRawCrtFns; +#define DebugAlloc (g_pMemOverrideRawCrtFns->pfn_malloc) +#define DebugFree (g_pMemOverrideRawCrtFns->pfn_free) +#elif defined( _X360 ) +#define DebugAlloc DmAllocatePool +#define DebugFree DmFreePool +#else +#define DebugAlloc malloc +#define DebugFree free +#endif + +#ifdef _WIN32 +int g_DefaultHeapFlags = _CrtSetDbgFlag( _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_ALLOC_MEM_DF ); +#else +int g_DefaultHeapFlags = 0; +#endif // win32 + +#if defined( _MEMTEST ) +static char s_szStatsMapName[32]; +static char s_szStatsComment[256]; +#endif + +#pragma optimize( "", off ) +//----------------------------------------------------------------------------- + +#if defined( USE_STACK_TRACES ) + +bool GetModuleFromAddress( void *address, char *pResult, int iLength ) +{ + return GetModuleNameFromAddress( address, pResult, iLength ); +} + +bool GetCallerModule( char *pDest, int iLength ) +{ + void *pCaller; + GetCallStack_Fast( &pCaller, 1, 2 ); + + return ( pCaller != 0 && GetModuleFromAddress( pCaller, pDest, iLength ) ); +} + +// +// Note: StackDescribe function is non-reentrant: +// Reason: Stack description is stored in a static buffer. +// Solution: Passing caller-allocated buffers would allow the +// function to become reentrant, however the current only client (FindOrCreateFilename) +// is synchronized with a heap mutex, after retrieving stack description the +// heap memory will be allocated to copy the text. +// + +char * StackDescribe( void * const *ppAddresses, int nMaxAddresses ) +{ + static char s_chStackDescription[ 32 * 1024 ]; + char *pchBuffer = s_chStackDescription; + +#if defined( SORT_STACK_TRACE_DESCRIPTION_DUMPS ) //Assuming StackDescribe is called iteratively on a sorted set of stacks (as in DumpStackStats()). We can save work by skipping unchanged parts at the beginning of the string. + static void *LastCallStack[STACK_TRACE_LENGTH] = { NULL }; + static char *pEndPos[STACK_TRACE_LENGTH] = { NULL }; + bool bUseExistingString = true; +#else + s_chStackDescription[ 0 ] = 0; +#endif + + int k; + for ( k = 0; k < nMaxAddresses; ++ k ) + { + if ( !ppAddresses[k] ) + break; + +#if defined( SORT_STACK_TRACE_DESCRIPTION_DUMPS ) + if( bUseExistingString && (k < STACK_TRACE_LENGTH) ) + { + if( ppAddresses[k] == LastCallStack[k] ) + { + pchBuffer = pEndPos[k]; + continue; + } + else + { + //everything from here on is invalidated + bUseExistingString = false; + for( int clearEntries = k; clearEntries < STACK_TRACE_LENGTH; ++clearEntries ) //wipe out unused entries + { + LastCallStack[clearEntries] = NULL; + pEndPos[clearEntries] = NULL; + } + //fall through to existing code + + if( k == 0 ) + *pchBuffer = '\0'; + else + sprintf( pchBuffer, "<--" ); + } + } +#endif + { + pchBuffer += strlen( pchBuffer ); + + char szTemp[MAX_PATH]; + szTemp[0] = '\0'; + uint32 iLine = 0; + uint32 iLineDisplacement = 0; + uint64 iSymbolDisplacement = 0; + if ( GetFileAndLineFromAddress( ppAddresses[k], szTemp, MAX_PATH, iLine, &iLineDisplacement ) ) + { + char const *pchFileName = szTemp + strlen( szTemp ); + for ( size_t numSlashesAllowed = 2; pchFileName > szTemp; --pchFileName ) + { + if ( *pchFileName == '\\' ) + { + if ( numSlashesAllowed-- ) + continue; + else + break; + } + } + sprintf( pchBuffer, iLineDisplacement ? "%s:%d+0x%I32X" : "%s:%d", pchFileName, iLine, iLineDisplacement ); + } + else if ( GetSymbolNameFromAddress( ppAddresses[k], szTemp, MAX_PATH, &iSymbolDisplacement ) ) + { + sprintf( pchBuffer, ( iSymbolDisplacement > 0 && !( iSymbolDisplacement >> 63 ) ) ? "%s+0x%llX" : "%s", szTemp, iSymbolDisplacement ); + } + else + { + sprintf( pchBuffer, "#0x%08p", ppAddresses[k] ); + } + + pchBuffer += strlen( pchBuffer ); + sprintf( pchBuffer, "<--" ); + +#if defined( SORT_STACK_TRACE_DESCRIPTION_DUMPS ) + if( k < STACK_TRACE_LENGTH ) + { + LastCallStack[k] = ppAddresses[k]; + pEndPos[k] = pchBuffer; + } +#endif + } + } + *pchBuffer = 0; + +#if defined( SORT_STACK_TRACE_DESCRIPTION_DUMPS ) + for( ; k < STACK_TRACE_LENGTH; ++k ) //wipe out unused entries + { + LastCallStack[k] = NULL; + pEndPos[k] = NULL; + } +#endif + + return s_chStackDescription; +} + +#else + +#define GetModuleFromAddress( address, pResult, iLength ) ( ( *pResult = 0 ), 0) +#define GetCallerModule( pDest, iLength ) false + +#endif + + +//----------------------------------------------------------------------------- + +// NOTE: This exactly mirrors the dbg header in the MSDEV crt +// eventually when we write our own allocator, we can kill this +struct CrtDbgMemHeader_t +{ + unsigned char m_Reserved[8]; + const char *m_pFileName; + int m_nLineNumber; + unsigned char m_Reserved2[16]; +}; + +struct Sentinal_t +{ + DWORD value[4]; +}; + +Sentinal_t g_HeadSentinelAllocated = +{ + 0xeee1beef, + 0xeee1f00d, + 0xbd122969, + 0xbeefbeef, +}; + +Sentinal_t g_HeadSentinelFree = +{ + 0xdeadbeef, + 0xbaadf00d, + 0xbd122969, + 0xdeadbeef, +}; + +Sentinal_t g_TailSentinel = +{ + 0xbaadf00d, + 0xbd122969, + 0xdeadbeef, + 0xbaadf00d, +}; + +const byte g_FreeFill = 0xdd; + +enum DbgMemHeaderBlockType_t +{ + BLOCKTYPE_FREE, + BLOCKTYPE_ALLOCATED +}; + +struct DbgMemHeader_t +#if !defined( _DEBUG ) || defined( _PS3 ) + : CrtDbgMemHeader_t +#endif +{ + size_t nLogicalSize; +#if defined( USE_STACK_TRACES ) + unsigned int nStatIndex; + byte reserved[16 - (sizeof(unsigned int) * 2)]; // MS allocator always returns mem aligned on 16 bytes, which some of our code depends on +#else + byte reserved[16 - sizeof(unsigned int)]; // MS allocator always returns mem aligned on 16 bytes, which some of our code depends on +#endif + Sentinal_t sentinal; +}; + +const int g_nRecentFrees = ( IsPC() ) ? 8192 : 512; +DbgMemHeader_t ** GetRecentFrees() { static DbgMemHeader_t **g_pRecentFrees = (DbgMemHeader_t**) +#ifdef _PS3 +g_pMemOverrideRawCrtFns->pfn_calloc +#else +calloc +#endif +( g_nRecentFrees, sizeof(DbgMemHeader_t *) ); +return g_pRecentFrees; } +uint32 volatile g_iNextFreeSlot; + +uint32 volatile g_break_BytesFree = 0xffffffff; + +void LMDReportInvalidBlock( DbgMemHeader_t *pHeader, const char *pszMessage ) +{ + char szMsg[256]; + if ( pHeader ) + { + sprintf( szMsg, "HEAP IS CORRUPT: %s (block 0x%x, %d bytes)\n", pszMessage, (size_t)( ((byte*) pHeader) + sizeof( DbgMemHeader_t ) ), pHeader->nLogicalSize ); + } + else + { + sprintf( szMsg, "HEAP IS CORRUPT: %s\n", pszMessage ); + } + Assert( !"HEAP IS CORRUPT!" ); + DebuggerBreak(); +} + +void LMDValidateBlock( DbgMemHeader_t *pHeader, bool bFreeList ) +{ + if ( memcmp( &pHeader->sentinal, bFreeList ? &g_HeadSentinelFree : &g_HeadSentinelAllocated, sizeof(Sentinal_t) ) != 0 ) + { + LMDReportInvalidBlock( pHeader, "Head sentinel corrupt" ); + } + if ( memcmp( ((Sentinal_t *)(( ((byte*) pHeader) + sizeof( DbgMemHeader_t ) + pHeader->nLogicalSize ))), &g_TailSentinel, sizeof(Sentinal_t) ) != 0 ) + { + LMDReportInvalidBlock( pHeader, "Tail sentinel corrupt" ); + } + if ( bFreeList ) + { + byte *pCur = (byte *)pHeader + sizeof(DbgMemHeader_t); + byte *pLimit = pCur + pHeader->nLogicalSize; + while ( pCur != pLimit ) + { + if ( *pCur++ != g_FreeFill ) + { + LMDReportInvalidBlock( pHeader, "Write after free" ); + } + } + } +} + + +//----------------------------------------------------------------------------- + +#if defined( _DEBUG ) && !defined( POSIX ) +#define GetCrtDbgMemHeader( pMem ) ((CrtDbgMemHeader_t*)((DbgMemHeader_t*)pMem - 1) - 1) +#elif defined( OSX ) +DbgMemHeader_t *GetCrtDbgMemHeader( void *pMem ); +#else +#define GetCrtDbgMemHeader( pMem ) ((DbgMemHeader_t*)(pMem) - 1) +#endif + +#if defined( USE_STACK_TRACES ) +#define GetAllocationStatIndex_Internal( pMem ) ( ((DbgMemHeader_t*)pMem - 1)->nStatIndex ) +#endif + +#ifdef OSX +DbgMemHeader_t *GetCrtDbgMemHeader( void *pMem ) +{ + size_t msize = malloc_size( pMem ); + return (DbgMemHeader_t *)( (char *)pMem + msize - sizeof(DbgMemHeader_t) ); +} +#endif + + +inline void *InternalMalloc( size_t nSize, const char *pFileName, int nLine ) +{ +#if defined( POSIX ) || defined( _PS3 ) + void *pAllocedMem = NULL; +#ifdef OSX + pAllocedMem = malloc_zone_malloc( malloc_default_zone(), nSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pAllocedMem ); +#elif defined( _PS3 ) + pAllocedMem = (g_pMemOverrideRawCrtFns->pfn_malloc)( nSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pAllocedMem; + *((void**)pInternalMem->m_Reserved2) = pAllocedMem; +#else + pAllocedMem = malloc( nSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pAllocedMem; +#endif + + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; + pInternalMem->nLogicalSize = nSize; + *((int*)pInternalMem->m_Reserved) = 0xf00df00d; + + pInternalMem->sentinal = g_HeadSentinelAllocated; + *( (Sentinal_t *)( ((byte*)pInternalMem) + sizeof( DbgMemHeader_t ) + nSize ) ) = g_TailSentinel; + LMDValidateBlock( pInternalMem, false ); + +#ifdef OSX + return pAllocedMem; +#else + return pInternalMem + 1; +#endif + +#else // WIN32 + DbgMemHeader_t *pInternalMem; +#if !defined( _DEBUG ) + pInternalMem = (DbgMemHeader_t *)malloc( nSize + sizeof(DbgMemHeader_t) ); + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; +#else + pInternalMem = (DbgMemHeader_t *)_malloc_dbg( nSize + sizeof(DbgMemHeader_t), _NORMAL_BLOCK, pFileName, nLine ); +#endif + + pInternalMem->nLogicalSize = nSize; + return pInternalMem + 1; +#endif // WIN32 +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +inline void *InternalMallocAligned( size_t nSize, size_t align, const char *pFileName, int nLine ) +{ +#if defined( POSIX ) || defined( _PS3 ) + void *pAllocedMem = NULL; +#ifdef OSX + pAllocedMem = malloc_zone_malloc( malloc_default_zone(), nSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pAllocedMem ); +#elif defined( _PS3 ) + size_t numWastedAlignPages = ( sizeof( DbgMemHeader_t ) / align ); + if ( align * numWastedAlignPages < sizeof( DbgMemHeader_t ) ) + ++ numWastedAlignPages; + size_t nSizeRequired = nSize + numWastedAlignPages*align + sizeof( Sentinal_t ); + pAllocedMem = (g_pMemOverrideRawCrtFns->pfn_memalign)( align, nSizeRequired ); + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( ((char*)pAllocedMem) + numWastedAlignPages*align ); + *((void**)pInternalMem->m_Reserved2) = pAllocedMem; +#else + pAllocedMem = malloc( nSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pAllocedMem; +#endif + + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; + pInternalMem->nLogicalSize = nSize; + *((int*)pInternalMem->m_Reserved) = 0xf00df00d; + + pInternalMem->sentinal = g_HeadSentinelAllocated; + *( (Sentinal_t *)( ((byte*)pInternalMem) + sizeof( DbgMemHeader_t ) + nSize ) ) = g_TailSentinel; + LMDValidateBlock( pInternalMem, false ); + +#ifdef OSX + return pAllocedMem; +#else + return pInternalMem + 1; +#endif + +#else // WIN32 + DbgMemHeader_t *pInternalMem; +#if !defined( _DEBUG ) + pInternalMem = (DbgMemHeader_t *)malloc( nSize + sizeof(DbgMemHeader_t) ); + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; +#else + pInternalMem = (DbgMemHeader_t *)_malloc_dbg( nSize + sizeof(DbgMemHeader_t), _NORMAL_BLOCK, pFileName, nLine ); +#endif + + pInternalMem->nLogicalSize = nSize; + return pInternalMem + 1; +#endif // WIN32 +} +#endif + +inline void *InternalRealloc( void *pMem, size_t nNewSize, const char *pFileName, int nLine ) +{ + if ( !pMem ) + return InternalMalloc( nNewSize, pFileName, nLine ); + +#ifdef POSIX + void *pNewAllocedMem = NULL; +#ifdef OSX + pNewAllocedMem = (DbgMemHeader_t *)malloc_zone_realloc( malloc_default_zone(), pMem, nNewSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pNewAllocedMem ); +#elif defined( _PS3 ) + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); + pNewAllocedMem = (DbgMemHeader_t *)(g_pMemOverrideRawCrtFns->pfn_realloc)( *((void**)pInternalMem->m_Reserved2), nNewSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); + pInternalMem = (DbgMemHeader_t *)pNewAllocedMem; + *((void**)pInternalMem->m_Reserved2) = pNewAllocedMem; +#else + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); + pNewAllocedMem = (DbgMemHeader_t *)realloc( pInternalMem, nNewSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); + pInternalMem = (DbgMemHeader_t *)pNewAllocedMem; +#endif + + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; + pInternalMem->nLogicalSize = static_cast<unsigned int>( nNewSize ); + *((int*)pInternalMem->m_Reserved) = 0xf00df00d; + + pInternalMem->sentinal = g_HeadSentinelAllocated; + *( (Sentinal_t *)( ((byte*)pInternalMem) + sizeof( DbgMemHeader_t ) + nNewSize ) ) = g_TailSentinel; + LMDValidateBlock( pInternalMem, false ); + +#ifdef OSX + return pNewAllocedMem; +#else + return pInternalMem + 1; +#endif + +#else // WIN32 + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; +#if !defined( _DEBUG ) + pInternalMem = (DbgMemHeader_t *)realloc( pInternalMem, nNewSize + sizeof(DbgMemHeader_t) ); + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; +#else + pInternalMem = (DbgMemHeader_t *)_realloc_dbg( pInternalMem, nNewSize + sizeof(DbgMemHeader_t), _NORMAL_BLOCK, pFileName, nLine ); +#endif + + pInternalMem->nLogicalSize = nNewSize; + return pInternalMem + 1; +#endif // WIN32 +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +inline void *InternalReallocAligned( void *pMem, size_t nNewSize, size_t align, const char *pFileName, int nLine ) +{ + if ( !pMem ) + return InternalMallocAligned( nNewSize, align, pFileName, nLine ); + +#ifdef POSIX + void *pNewAllocedMem = NULL; +#ifdef OSX + pNewAllocedMem = (DbgMemHeader_t *)malloc_zone_realloc( malloc_default_zone(), pMem, nNewSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pNewAllocedMem ); +#elif defined( _PS3 ) + size_t numWastedAlignPages = ( sizeof( DbgMemHeader_t ) / align ); + if ( align * numWastedAlignPages < sizeof( DbgMemHeader_t ) ) + ++ numWastedAlignPages; + size_t nSizeRequired = nNewSize + numWastedAlignPages*align + sizeof( Sentinal_t ); + + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); + pNewAllocedMem = (DbgMemHeader_t *)(g_pMemOverrideRawCrtFns->pfn_reallocalign)( *((void**)pInternalMem->m_Reserved2), nSizeRequired, align ); + pInternalMem = GetCrtDbgMemHeader( ((char*)pNewAllocedMem) + numWastedAlignPages*align ); + *((void**)pInternalMem->m_Reserved2) = pNewAllocedMem; +#else + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); + pNewAllocedMem = (DbgMemHeader_t *)realloc( pInternalMem, nNewSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); + pInternalMem = (DbgMemHeader_t *)pNewAllocedMem; +#endif + + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; + pInternalMem->nLogicalSize = static_cast<unsigned int>( nNewSize ); + *((int*)pInternalMem->m_Reserved) = 0xf00df00d; + + pInternalMem->sentinal = g_HeadSentinelAllocated; + *( (Sentinal_t *)( ((byte*)pInternalMem) + sizeof( DbgMemHeader_t ) + nNewSize ) ) = g_TailSentinel; + LMDValidateBlock( pInternalMem, false ); + +#ifdef OSX + return pNewAllocedMem; +#else + return pInternalMem + 1; +#endif + +#else // WIN32 + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; +#if !defined( _DEBUG ) + pInternalMem = (DbgMemHeader_t *)realloc( pInternalMem, nNewSize + sizeof(DbgMemHeader_t) ); + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; +#else + pInternalMem = (DbgMemHeader_t *)_realloc_dbg( pInternalMem, nNewSize + sizeof(DbgMemHeader_t), _NORMAL_BLOCK, pFileName, nLine ); +#endif + + pInternalMem->nLogicalSize = nNewSize; + return pInternalMem + 1; +#endif // WIN32 +} +#endif + +inline void InternalFree( void *pMem ) +{ + if ( !pMem ) + return; + + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; + +#if defined( POSIX ) + // Record it in recent free blocks list + DbgMemHeader_t **pRecentFrees = GetRecentFrees(); + uint32 iNextSlot = ThreadInterlockedIncrement( &g_iNextFreeSlot ); + iNextSlot %= g_nRecentFrees; + + if ( memcmp( &pInternalMem->sentinal, &g_HeadSentinelAllocated, sizeof( Sentinal_t ) ) != 0 ) + { + Assert( !"Double Free or Corrupt Block Header!" ); + DebuggerBreak(); + } + LMDValidateBlock( pInternalMem, false ); + if ( g_break_BytesFree == pInternalMem->nLogicalSize ) + { + DebuggerBreak(); + } + pInternalMem->sentinal = g_HeadSentinelFree; + memset( pMem, g_FreeFill, pInternalMem->nLogicalSize ); + + DbgMemHeader_t *pToFree = pInternalMem; + if ( pInternalMem->nLogicalSize < 16*1024 ) + { + pToFree = pRecentFrees[iNextSlot]; + pRecentFrees[iNextSlot] = pInternalMem; + + if ( pToFree ) + { + LMDValidateBlock( pToFree, true ); + } + } + + // Validate several last frees + for ( uint32 k = iNextSlot - 1, iteration = 0; iteration < 10; ++ iteration, -- k ) + { + if ( DbgMemHeader_t *pLastFree = pRecentFrees[ k % g_nRecentFrees ] ) + { + LMDValidateBlock( pLastFree, true ); + } + } + + if ( !pToFree ) + return; + +#ifdef OSX + malloc_zone_free( malloc_default_zone(), pToFree ); +#elif defined( _PS3 ) + (g_pMemOverrideRawCrtFns->pfn_free)( *((void**)pToFree->m_Reserved2) ); +#elif LINUX + free( pToFree ); +#else + free( pToFree ); +#endif +#elif defined( _DEBUG ) + _free_dbg( pInternalMem, _NORMAL_BLOCK ); +#else + free( pInternalMem ); +#endif +} + +inline size_t InternalMSize( void *pMem ) +{ +#if defined( _PS3 ) + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); + return pInternalMem->nLogicalSize; +#elif defined(POSIX) + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); + return pInternalMem->nLogicalSize; +#elif !defined(_DEBUG) + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); + return _msize( pInternalMem ) - sizeof(DbgMemHeader_t); +#else + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; + return _msize_dbg( pInternalMem, _NORMAL_BLOCK ) - sizeof(DbgMemHeader_t); +#endif +} + +inline size_t InternalLogicalSize( void *pMem ) +{ +#if defined(POSIX) + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); +#elif !defined(_DEBUG) + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; +#else + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; +#endif + return pInternalMem->nLogicalSize; +} + +#ifndef _DEBUG +#define _CrtDbgReport( nRptType, szFile, nLine, szModule, pMsg ) 0 +#endif + +//----------------------------------------------------------------------------- + + +// Custom allocator protects this module from recursing on operator new +template <class T> +class CNoRecurseAllocator +{ +public: + // type definitions + typedef T value_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + CNoRecurseAllocator() {} + CNoRecurseAllocator(const CNoRecurseAllocator&) {} + template <class U> CNoRecurseAllocator(const CNoRecurseAllocator<U>&) {} + ~CNoRecurseAllocator(){} + + // rebind allocator to type U + template <class U > struct rebind { typedef CNoRecurseAllocator<U> other; }; + + // return address of values + pointer address (reference value) const { return &value; } + + const_pointer address (const_reference value) const { return &value;} + size_type max_size() const { return INT_MAX; } + + pointer allocate(size_type num, const void* = 0) { return (pointer)DebugAlloc(num * sizeof(T)); } + void deallocate (pointer p, size_type num) { DebugFree(p); } + void construct(pointer p, const T& value) { new((void*)p)T(value); } + void destroy (pointer p) { p->~T(); } +}; + +template <class T1, class T2> +bool operator==(const CNoRecurseAllocator<T1>&, const CNoRecurseAllocator<T2>&) +{ + return true; +} + +template <class T1, class T2> +bool operator!=(const CNoRecurseAllocator<T1>&, const CNoRecurseAllocator<T2>&) +{ + return false; +} + +class CStringLess +{ +public: + bool operator()(const char *pszLeft, const char *pszRight ) const + { + return ( V_tier0_stricmp( pszLeft, pszRight ) < 0 ); + } +}; + +//----------------------------------------------------------------------------- + +#pragma warning( disable:4074 ) // warning C4074: initializers put in compiler reserved initialization area +#pragma init_seg( compiler ) + +//----------------------------------------------------------------------------- +// NOTE! This should never be called directly from leaf code +// Just use new,delete,malloc,free etc. They will call into this eventually +//----------------------------------------------------------------------------- +class CDbgMemAlloc : public IMemAlloc +{ +public: + CDbgMemAlloc(); + virtual ~CDbgMemAlloc(); + + // Release versions + virtual void *Alloc( size_t nSize ); + virtual void *Realloc( void *pMem, size_t nSize ); + virtual void Free( void *pMem ); + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize ); + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + virtual void *AllocAlign( size_t nSize, size_t align ); + virtual void *AllocAlign( size_t nSize, size_t align, const char *pFileName, int nLine ); + virtual void *ReallocAlign( void *pMem, size_t nSize, size_t align ); + virtual void *ReallocAlign( void *pMem, size_t nSize, size_t align, const char *pFileName, int nLine ); +#endif + + // Debug versions + virtual void *Alloc( size_t nSize, const char *pFileName, int nLine ); + virtual void *Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ); + virtual void Free( void *pMem, const char *pFileName, int nLine ); + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ); + + virtual void *RegionAlloc( int region, size_t nSize ) { return Alloc( nSize ); } + virtual void *RegionAlloc( int region, size_t nSize, const char *pFileName, int nLine ) { return Alloc( nSize, pFileName, nLine ); } + + // Returns the size of a particular allocation (NOTE: may be larger than the size requested!) + virtual size_t GetSize( void *pMem ); + + // Force file + line information for an allocation + virtual void PushAllocDbgInfo( const char *pFileName, int nLine ); + virtual void PopAllocDbgInfo(); + + virtual int32 CrtSetBreakAlloc( int32 lNewBreakAlloc ); + virtual int CrtSetReportMode( int nReportType, int nReportMode ); + virtual int CrtIsValidHeapPointer( const void *pMem ); + virtual int CrtIsValidPointer( const void *pMem, unsigned int size, int access ); + virtual int CrtCheckMemory( void ); + virtual int CrtSetDbgFlag( int nNewFlag ); + virtual void CrtMemCheckpoint( _CrtMemState *pState ); + + // handles storing allocation info for coroutines + virtual uint32 GetDebugInfoSize(); + virtual void SaveDebugInfo( void *pvDebugInfo ); + virtual void RestoreDebugInfo( const void *pvDebugInfo ); + virtual void InitDebugInfo( void *pvDebugInfo, const char *pchRootFileName, int nLine ); + + // FIXME: Remove when we have our own allocator + virtual void* CrtSetReportFile( int nRptType, void* hFile ); + virtual void* CrtSetReportHook( void* pfnNewHook ); + virtual int CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * szFormat ); + + virtual int heapchk(); + + virtual bool IsDebugHeap() { return true; } + + virtual int GetVersion() { return MEMALLOC_VERSION; } + + virtual void CompactHeap() + { +#if defined( _X360 ) && defined( _DEBUG ) + HeapCompact( GetProcessHeap(), 0 ); +#endif + } + + virtual void CompactIncremental() {} + virtual void OutOfMemory( size_t nBytesAttempted = 0 ) {} + + virtual MemAllocFailHandler_t SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ) { return NULL; } // debug heap doesn't attempt retries + + void SetStatsExtraInfo( const char *pMapName, const char *pComment ) + { +#if defined( _MEMTEST ) + strncpy( s_szStatsMapName, pMapName, sizeof( s_szStatsMapName ) ); + s_szStatsMapName[sizeof( s_szStatsMapName ) - 1] = '\0'; + + strncpy( s_szStatsComment, pComment, sizeof( s_szStatsComment ) ); + s_szStatsComment[sizeof( s_szStatsComment ) - 1] = '\0'; +#endif + } + + virtual size_t MemoryAllocFailed(); + void SetCRTAllocFailed( size_t nMemSize ); + + enum + { + BYTE_COUNT_16 = 0, + BYTE_COUNT_32, + BYTE_COUNT_128, + BYTE_COUNT_2048, + BYTE_COUNT_GREATER, + + NUM_BYTE_COUNT_BUCKETS + }; + +private: + struct MemInfo_t + { +#if defined( USE_STACK_TRACES ) + DECLARE_CALLSTACKSTATSTRUCT(); + DECLARE_CALLSTACKSTATSTRUCT_FIELDDESCRIPTION(); +#endif + + MemInfo_t() + { + memset( this, 0, sizeof(*this) ); + } + + // Size in bytes + size_t m_nCurrentSize; + size_t m_nPeakSize; + size_t m_nTotalSize; + size_t m_nOverheadSize; + size_t m_nPeakOverheadSize; + + // Count in terms of # of allocations + int m_nCurrentCount; + int m_nPeakCount; + int m_nTotalCount; + + int m_nSumTargetRange; + int m_nCurTargetRange; + int m_nMaxTargetRange; + + // Count in terms of # of allocations of a particular size + int m_pCount[NUM_BYTE_COUNT_BUCKETS]; + + // Time spent allocating + deallocating (microseconds) + int64 m_nTime; + }; + + struct MemInfoKey_FileLine_t + { + MemInfoKey_FileLine_t( const char *pFileName, int line ) : m_pFileName(pFileName), m_nLine(line) {} + bool operator<( const MemInfoKey_FileLine_t &key ) const + { + int iret = V_tier0_stricmp( m_pFileName, key.m_pFileName ); + if ( iret < 0 ) + return true; + + if ( iret > 0 ) + return false; + + return m_nLine < key.m_nLine; + } + + const char *m_pFileName; + int m_nLine; + }; + + // NOTE: Deliberately using STL here because the UTL stuff + // is a client of this library; want to avoid circular dependency + + // Maps file name to info + typedef std::map< MemInfoKey_FileLine_t, MemInfo_t, std::less<MemInfoKey_FileLine_t>, CNoRecurseAllocator<std::pair<const MemInfoKey_FileLine_t, MemInfo_t> > > StatMap_FileLine_t; + typedef StatMap_FileLine_t::iterator StatMapIter_FileLine_t; + typedef StatMap_FileLine_t::value_type StatMapEntry_FileLine_t; + + typedef std::set<const char *, CStringLess, CNoRecurseAllocator<const char *> > Filenames_t; + + // Heap reporting method + typedef void (*HeapReportFunc_t)( char const *pFormat, ... ); + +private: + // Returns the actual debug info + virtual void GetActualDbgInfo( const char *&pFileName, int &nLine ); + + // Finds the file in our map + MemInfo_t &FindOrCreateEntry( const char *pFileName, int line ); + const char *FindOrCreateFilename( const char *pFileName ); + +#if defined( USE_STACK_TRACES ) + int GetCallStackForIndex( unsigned int index, void **pCallStackOut, int iMaxEntriesOut ); + friend int GetAllocationCallStack( void *mem, void **pCallStackOut, int iMaxEntriesOut ); +#endif + + // Updates stats + virtual void RegisterAllocation( const char *pFileName, int nLine, size_t nLogicalSize, size_t nActualSize, unsigned nTime ); + virtual void RegisterDeallocation( const char *pFileName, int nLine, size_t nLogicalSize, size_t nActualSize, unsigned nTime ); +#if defined( USE_STACK_TRACES ) + void RegisterAllocation( unsigned int nStatIndex, size_t nLogicalSize, size_t nActualSize, unsigned nTime ); + void RegisterDeallocation( unsigned int nStatIndex, size_t nLogicalSize, size_t nActualSize, unsigned nTime ); +#endif + void RegisterAllocation( MemInfo_t &info, size_t nLogicalSize, size_t nActualSize, unsigned nTime ); + void RegisterDeallocation( MemInfo_t &info, size_t nLogicalSize, size_t nActualSize, unsigned nTime ); + + // Gets the allocation file name + const char *GetAllocatonFileName( void *pMem ); + int GetAllocatonLineNumber( void *pMem ); + + // FIXME: specify a spew output func for dumping stats + // Stat output + void DumpMemInfo( const char *pAllocationName, int line, const MemInfo_t &info ); + void DumpFileStats(); +#if defined( USE_STACK_TRACES ) + void DumpMemInfo( void * const CallStack[STACK_TRACE_LENGTH], const MemInfo_t &info ); + void DumpCallStackFlow( char const *pchFileBase ); +#endif + virtual void DumpStats(); + virtual void DumpStatsFileBase( char const *pchFileBase ); + virtual void DumpBlockStats( void *p ); + virtual void GlobalMemoryStatus( size_t *pUsedMemory, size_t *pFreeMemory ); + + virtual size_t ComputeMemoryUsedBy( char const *pchSubStr ); + + virtual IVirtualMemorySection * AllocateVirtualMemorySection( size_t numMaxBytes ) + { +#if defined( _GAMECONSOLE ) || defined( _WIN32 ) + extern IVirtualMemorySection * VirtualMemoryManager_AllocateVirtualMemorySection( size_t numMaxBytes ); + return VirtualMemoryManager_AllocateVirtualMemorySection( numMaxBytes ); +#else + return NULL; +#endif + } + + virtual int GetGenericMemoryStats( GenericMemoryStat_t **ppMemoryStats ) + { + // TODO: reuse code from GlobalMemoryStatus (though this is only really useful when using CStdMemAlloc...) + return 0; + } + +private: + StatMap_FileLine_t m_StatMap_FileLine; +#if defined( USE_STACK_TRACES ) + typedef CCallStackStatsGatherer<MemInfo_t, STACK_TRACE_LENGTH, GetCallStack_Fast, CCallStackStatsGatherer_StatMutexPool<128>, CNoRecurseAllocator> CallStackStatsType_t; + CallStackStatsType_t m_CallStackStats; +#endif + + MemInfo_t m_GlobalInfo; + CFastTimer m_Timer; + bool m_bInitialized; + Filenames_t m_Filenames; + + HeapReportFunc_t m_OutputFunc; + + static size_t s_pCountSizes[NUM_BYTE_COUNT_BUCKETS]; + static const char *s_pCountHeader[NUM_BYTE_COUNT_BUCKETS]; + + size_t m_sMemoryAllocFailed; +}; + +static char const *g_pszUnknown = "unknown"; + +#if defined( USE_STACK_TRACES ) +BEGIN_STATSTRUCTDESCRIPTION( CDbgMemAlloc::MemInfo_t ) + WRITE_STATSTRUCT_FIELDDESCRIPTION(); +END_STATSTRUCTDESCRIPTION() + + +BEGIN_STATSTRUCTFIELDDESCRIPTION( CDbgMemAlloc::MemInfo_t ) + DEFINE_STATSTRUCTFIELD( m_nCurrentSize, BasicStatStructFieldDesc, ( BSSFT_SIZE_T, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD( m_nPeakSize, BasicStatStructFieldDesc, ( BSSFT_SIZE_T, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD( m_nTotalSize, BasicStatStructFieldDesc, ( BSSFT_SIZE_T, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD( m_nOverheadSize, BasicStatStructFieldDesc, ( BSSFT_SIZE_T, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD( m_nPeakOverheadSize, BasicStatStructFieldDesc, ( BSSFT_SIZE_T, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD( m_nCurrentCount, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD( m_nPeakCount, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD( m_nTotalCount, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD( m_nSumTargetRange, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD( m_nCurTargetRange, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD( m_nMaxTargetRange, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD_ARRAYENTRY( m_pCount, BYTE_COUNT_16, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD_ARRAYENTRY( m_pCount, BYTE_COUNT_32, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD_ARRAYENTRY( m_pCount, BYTE_COUNT_128, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD_ARRAYENTRY( m_pCount, BYTE_COUNT_2048, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD_ARRAYENTRY( m_pCount, BYTE_COUNT_GREATER, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) + DEFINE_STATSTRUCTFIELD( m_nTime, BasicStatStructFieldDesc, ( BSSFT_INT64, BSSFCM_ADD ) ) +END_STATSTRUCTFIELDDESCRIPTION() +#endif + + +//----------------------------------------------------------------------------- + +const int DBG_INFO_STACK_DEPTH = 32; + +struct DbgInfoStack_t +{ + const char *m_pFileName; + int m_nLine; +}; + +#ifdef _PS3 +#ifndef _CERT +extern TLSGlobals * ( *g_pfnElfGetTlsGlobals )(); +#define IfDbgInfoIsReady() if ( TLSGlobals *IfDbgInfoIsReady_pTlsGlobals = g_pfnElfGetTlsGlobals ? g_pfnElfGetTlsGlobals() : NULL ) +#else +#define IfDbgInfoIsReady() if ( TLSGlobals *IfDbgInfoIsReady_pTlsGlobals = GetTLSGlobals() ) +#endif +#define g_DbgInfoStack ( ( DbgInfoStack_t *& ) IfDbgInfoIsReady_pTlsGlobals->pMallocDbgInfoStack ) +#define g_nDbgInfoStackDepth ( IfDbgInfoIsReady_pTlsGlobals->nMallocDbgInfoStackDepth ) +#else +CTHREADLOCALPTR( DbgInfoStack_t) g_DbgInfoStack CONSTRUCT_EARLY; +CTHREADLOCALINT g_nDbgInfoStackDepth CONSTRUCT_EARLY; +#define IfDbgInfoIsReady() if (true) +#endif + +#ifdef _PS3 +struct CDbgMemAlloc_GetRawCrtMemOverrideFuncs_Early +{ + CDbgMemAlloc_GetRawCrtMemOverrideFuncs_Early() + { + malloc_managed_size mms; + mms.current_inuse_size = 0x12345678; + mms.current_system_size = 0x09ABCDEF; + mms.max_system_size = 0; + int iResult = malloc_stats( &mms ); + g_pMemOverrideRawCrtFns = reinterpret_cast< MemOverrideRawCrtFunctions_t * >( iResult ); + } +} +g_CDbgMemAlloc_GetRawCrtMemOverrideFuncs_Early CONSTRUCT_EARLY; +#endif + +//----------------------------------------------------------------------------- +// Singleton... +//----------------------------------------------------------------------------- +static CDbgMemAlloc s_DbgMemAlloc CONSTRUCT_EARLY; + +#ifdef _PS3 + +IMemAlloc *g_pMemAllocInternalPS3 = &s_DbgMemAlloc; +PLATFORM_OVERRIDE_MEM_ALLOC_INTERNAL_PS3_IMPL + +#else // !_PS3 + +#ifndef TIER0_VALIDATE_HEAP +IMemAlloc *g_pMemAlloc CONSTRUCT_EARLY = &s_DbgMemAlloc; +#else +IMemAlloc *g_pActualAlloc = &s_DbgMemAlloc; +#endif + +#endif // _PS3 + + +//----------------------------------------------------------------------------- + +CThreadMutex g_DbgMemMutex CONSTRUCT_EARLY; + +#define HEAP_LOCK() AUTO_LOCK( g_DbgMemMutex ) + + +//----------------------------------------------------------------------------- +// Byte count buckets +//----------------------------------------------------------------------------- +size_t CDbgMemAlloc::s_pCountSizes[CDbgMemAlloc::NUM_BYTE_COUNT_BUCKETS] = +{ + 16, 32, 128, 2048, INT_MAX +}; + +const char *CDbgMemAlloc::s_pCountHeader[CDbgMemAlloc::NUM_BYTE_COUNT_BUCKETS] = +{ + "<=16 byte allocations", + "17-32 byte allocations", + "33-128 byte allocations", + "129-2048 byte allocations", + ">2048 byte allocations" +}; + + +size_t g_TargetCountRangeMin = 0, g_TargetCountRangeMax = 0; + +//----------------------------------------------------------------------------- +// Standard output +//----------------------------------------------------------------------------- +static FILE* s_DbgFile; + +static void DefaultHeapReportFunc( char const *pFormat, ... ) +{ + va_list args; + va_start( args, pFormat ); + vfprintf( s_DbgFile, pFormat, args ); + va_end( args ); +} + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CDbgMemAlloc::CDbgMemAlloc() : m_sMemoryAllocFailed( (size_t)0 ) +{ + CClockSpeedInit::Init(); + + m_OutputFunc = DefaultHeapReportFunc; + m_bInitialized = true; + + if ( !IsDebug() && !IsX360() ) + { + Plat_DebugString( "USE_MEM_DEBUG is enabled in a release build. Don't check this in!\n" ); + } + +#ifdef _PS3 + g_pMemAllocInternalPS3 = &s_DbgMemAlloc; + PLATFORM_OVERRIDE_MEM_ALLOC_INTERNAL_PS3.m_pMemAllocCached = &s_DbgMemAlloc; + malloc_managed_size mms; + mms.current_inuse_size = 0x12345678; + mms.current_system_size = 0x09ABCDEF; + mms.max_system_size = reinterpret_cast< size_t >( this ); + int iResult = malloc_stats( &mms ); + g_pMemOverrideRawCrtFns = reinterpret_cast< MemOverrideRawCrtFunctions_t * >( iResult ); +#endif +} + +CDbgMemAlloc::~CDbgMemAlloc() +{ + Filenames_t::const_iterator iter = m_Filenames.begin(); + while(iter != m_Filenames.end()) + { + char *pFileName = (char*)(*iter); + free( pFileName ); + iter++; + } + m_bInitialized = false; +} + + +//----------------------------------------------------------------------------- +// Release versions +//----------------------------------------------------------------------------- + +void *CDbgMemAlloc::Alloc( size_t nSize ) +{ +/* + // NOTE: Uncomment this to find unknown allocations + const char *pFileName = g_pszUnknown; + int nLine; + GetActualDbgInfo( pFileName, nLine ); + if (pFileName == g_pszUnknown) + { + int x = 3; + } +*/ + char szModule[MAX_PATH]; + if ( GetCallerModule( szModule, MAX_PATH ) ) + { + return Alloc( nSize, szModule, 0 ); + } + else + { + return Alloc( nSize, g_pszUnknown, 0 ); + } +// return malloc( nSize ); +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +void *CDbgMemAlloc::AllocAlign( size_t nSize, size_t align ) +{ +/* + // NOTE: Uncomment this to find unknown allocations + const char *pFileName = g_pszUnknown; + int nLine; + GetActualDbgInfo( pFileName, nLine ); + if (pFileName == g_pszUnknown) + { + int x = 3; + } +*/ + char szModule[MAX_PATH]; + if ( GetCallerModule( szModule, MAX_PATH ) ) + { + return AllocAlign( nSize, align, szModule, 0 ); + } + else + { + return AllocAlign( nSize, align, g_pszUnknown, 0 ); + } +// return malloc( nSize ); +} +#endif + +void *CDbgMemAlloc::Realloc( void *pMem, size_t nSize ) +{ +/* + // NOTE: Uncomment this to find unknown allocations + const char *pFileName = g_pszUnknown; + int nLine; + GetActualDbgInfo( pFileName, nLine ); + if (pFileName == g_pszUnknown) + { + int x = 3; + } +*/ + // FIXME: Should these gather stats? + char szModule[MAX_PATH]; + if ( GetCallerModule( szModule, MAX_PATH ) ) + { + return Realloc( pMem, nSize, szModule, 0 ); + } + else + { + return Realloc( pMem, nSize, g_pszUnknown, 0 ); + } +// return realloc( pMem, nSize ); +} + +void CDbgMemAlloc::Free( void *pMem ) +{ + // FIXME: Should these gather stats? + Free( pMem, g_pszUnknown, 0 ); +// free( pMem ); +} + +void *CDbgMemAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize ) +{ + return NULL; +} + + +//----------------------------------------------------------------------------- +// Force file + line information for an allocation +//----------------------------------------------------------------------------- +void CDbgMemAlloc::PushAllocDbgInfo( const char *pFileName, int nLine ) +{ + IfDbgInfoIsReady() + { + + if ( g_DbgInfoStack == NULL ) + { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); + g_nDbgInfoStackDepth = -1; + } + + ++g_nDbgInfoStackDepth; + Assert( g_nDbgInfoStackDepth < DBG_INFO_STACK_DEPTH ); + g_DbgInfoStack[g_nDbgInfoStackDepth].m_pFileName = FindOrCreateFilename( pFileName ); + g_DbgInfoStack[g_nDbgInfoStackDepth].m_nLine = nLine; + + } +} + +void CDbgMemAlloc::PopAllocDbgInfo() +{ + IfDbgInfoIsReady() + { + + if ( g_DbgInfoStack == NULL ) + { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); + g_nDbgInfoStackDepth = -1; + } + + --g_nDbgInfoStackDepth; + Assert( g_nDbgInfoStackDepth >= -1 ); + + } +} + + +//----------------------------------------------------------------------------- +// handles storing allocation info for coroutines +//----------------------------------------------------------------------------- +uint32 CDbgMemAlloc::GetDebugInfoSize() +{ + return sizeof( DbgInfoStack_t ) * DBG_INFO_STACK_DEPTH + sizeof( int32 ); +} + +void CDbgMemAlloc::SaveDebugInfo( void *pvDebugInfo ) +{ + IfDbgInfoIsReady() + { + if ( g_DbgInfoStack == NULL ) + { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); + g_nDbgInfoStackDepth = -1; + } + + int32 *pnStackDepth = (int32*) pvDebugInfo; + *pnStackDepth = g_nDbgInfoStackDepth; + memcpy( pnStackDepth+1, &g_DbgInfoStack[0], sizeof( DbgInfoStack_t ) * DBG_INFO_STACK_DEPTH ); + } +} + +void CDbgMemAlloc::RestoreDebugInfo( const void *pvDebugInfo ) +{ + IfDbgInfoIsReady() + { + if ( g_DbgInfoStack == NULL ) + { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); + g_nDbgInfoStackDepth = -1; + } + + const int32 *pnStackDepth = (const int32*) pvDebugInfo; + g_nDbgInfoStackDepth = *pnStackDepth; + memcpy( &g_DbgInfoStack[0], pnStackDepth+1, sizeof( DbgInfoStack_t ) * DBG_INFO_STACK_DEPTH ); + } +} + +void CDbgMemAlloc::InitDebugInfo( void *pvDebugInfo, const char *pchRootFileName, int nLine ) +{ + int32 *pnStackDepth = (int32*) pvDebugInfo; + + if( pchRootFileName ) + { + *pnStackDepth = 0; + + DbgInfoStack_t *pStackRoot = (DbgInfoStack_t *)(pnStackDepth + 1); + pStackRoot->m_pFileName = FindOrCreateFilename( pchRootFileName ); + pStackRoot->m_nLine = nLine; + } + else + { + *pnStackDepth = -1; + } + +} + + +//----------------------------------------------------------------------------- +// Returns the actual debug info +//----------------------------------------------------------------------------- +void CDbgMemAlloc::GetActualDbgInfo( const char *&pFileName, int &nLine ) +{ +#if defined( USE_STACK_TRACES_DETAILED ) + return; +#endif + + IfDbgInfoIsReady() + { + + if ( g_DbgInfoStack == NULL ) + { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); + g_nDbgInfoStackDepth = -1; + } + + if ( g_nDbgInfoStackDepth >= 0 && g_DbgInfoStack[0].m_pFileName) + { + pFileName = g_DbgInfoStack[0].m_pFileName; + nLine = g_DbgInfoStack[0].m_nLine; + } + + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +const char *CDbgMemAlloc::FindOrCreateFilename( const char *pFileName ) +{ + // If we created it for the first time, actually *allocate* the filename memory + HEAP_LOCK(); + // This is necessary for shutdown conditions: the file name is stored + // in some piece of memory in a DLL; if that DLL becomes unloaded, + // we'll have a pointer to crap memory + + if ( !pFileName ) + { + pFileName = g_pszUnknown; + } + +#if defined( USE_STACK_TRACES_DETAILED ) +{ + + // Walk the stack to determine what's causing the allocation + void *arrStackAddresses[ 10 ] = { 0 }; + int numStackAddrRetrieved = GetCallStack_Fast( arrStackAddresses, 10, 2 ); //Skip this function, and either CDbgMemAlloc::Alloc() or CDbgMemAlloc::Realloc() + char *szStack = StackDescribe( arrStackAddresses, numStackAddrRetrieved ); + if ( szStack && *szStack ) + { + pFileName = szStack; // Use the stack description for the allocation + } + +} +#endif // #if defined( USE_STACK_TRACES_DETAILED ) + + char *pszFilenameCopy; + Filenames_t::const_iterator iter = m_Filenames.find( pFileName ); + if ( iter == m_Filenames.end() ) + { + size_t nLen = strlen(pFileName) + 1; + pszFilenameCopy = (char *)DebugAlloc( nLen ); + memcpy( pszFilenameCopy, pFileName, nLen ); + m_Filenames.insert( pszFilenameCopy ); + } + else + { + pszFilenameCopy = (char *)(*iter); + } + + return pszFilenameCopy; +} + +//----------------------------------------------------------------------------- +// Finds the file in our map +//----------------------------------------------------------------------------- +CDbgMemAlloc::MemInfo_t &CDbgMemAlloc::FindOrCreateEntry( const char *pFileName, int line ) +{ + // Oh how I love crazy STL. retval.first == the StatMapIter_t in the std::pair + // retval.first->second == the MemInfo_t that's part of the StatMapIter_t + std::pair<StatMapIter_FileLine_t, bool> retval; + retval = m_StatMap_FileLine.insert( StatMapEntry_FileLine_t( MemInfoKey_FileLine_t( pFileName, line ), MemInfo_t() ) ); + return retval.first->second; +} + +#if defined( USE_STACK_TRACES ) +int CDbgMemAlloc::GetCallStackForIndex( unsigned int index, void **pCallStackOut, int iMaxEntriesOut ) +{ + if( iMaxEntriesOut > STACK_TRACE_LENGTH ) + iMaxEntriesOut = STACK_TRACE_LENGTH; + + CallStackStatsType_t::StackReference stackRef = m_CallStackStats.GetCallStackForIndex( index ); + + memcpy( pCallStackOut, stackRef, iMaxEntriesOut * sizeof( void * ) ); + for( int i = 0; i != iMaxEntriesOut; ++i ) + { + if( pCallStackOut[i] == NULL ) + return i; + } + return iMaxEntriesOut; +} +#endif + + +//----------------------------------------------------------------------------- +// Updates stats +//----------------------------------------------------------------------------- +void CDbgMemAlloc::RegisterAllocation( const char *pFileName, int nLine, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) +{ + HEAP_LOCK(); + RegisterAllocation( m_GlobalInfo, nLogicalSize, nActualSize, nTime ); + RegisterAllocation( FindOrCreateEntry( pFileName, nLine ), nLogicalSize, nActualSize, nTime ); +} + +void CDbgMemAlloc::RegisterDeallocation( const char *pFileName, int nLine, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) +{ + HEAP_LOCK(); + RegisterDeallocation( m_GlobalInfo, nLogicalSize, nActualSize, nTime ); + RegisterDeallocation( FindOrCreateEntry( pFileName, nLine ), nLogicalSize, nActualSize, nTime ); +} + +#if defined( USE_STACK_TRACES ) +void CDbgMemAlloc::RegisterAllocation( unsigned int nStatIndex, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) +{ + HEAP_LOCK(); + RegisterAllocation( m_GlobalInfo, nLogicalSize, nActualSize, nTime ); + CCallStackStatsGatherer_StructAccessor_AutoLock<MemInfo_t> entryAccessor = m_CallStackStats.GetEntry( nStatIndex ); + RegisterAllocation( *entryAccessor.GetStruct(), nLogicalSize, nActualSize, nTime ); +} + +void CDbgMemAlloc::RegisterDeallocation( unsigned int nStatIndex, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) +{ + HEAP_LOCK(); + RegisterDeallocation( m_GlobalInfo, nLogicalSize, nActualSize, nTime ); + CCallStackStatsGatherer_StructAccessor_AutoLock<MemInfo_t> entryAccessor = m_CallStackStats.GetEntry( nStatIndex ); + RegisterDeallocation( *entryAccessor.GetStruct(), nLogicalSize, nActualSize, nTime ); +} +#endif + +void CDbgMemAlloc::RegisterAllocation( MemInfo_t &info, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) +{ + ++info.m_nCurrentCount; + ++info.m_nTotalCount; + if (info.m_nCurrentCount > info.m_nPeakCount) + { + info.m_nPeakCount = info.m_nCurrentCount; + } + + info.m_nCurrentSize += nLogicalSize; + info.m_nTotalSize += nLogicalSize; + if (info.m_nCurrentSize > info.m_nPeakSize) + { + info.m_nPeakSize = info.m_nCurrentSize; + } + + if ( nLogicalSize > g_TargetCountRangeMin && nLogicalSize <= g_TargetCountRangeMax ) + { + info.m_nSumTargetRange++; + info.m_nCurTargetRange++; + if ( info.m_nCurTargetRange > info.m_nMaxTargetRange ) + { + info.m_nMaxTargetRange = info.m_nCurTargetRange; + } + } + + for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) + { + if (nLogicalSize <= s_pCountSizes[i]) + { + ++info.m_pCount[i]; + break; + } + } + + Assert( info.m_nPeakCount >= info.m_nCurrentCount ); + Assert( info.m_nPeakSize >= info.m_nCurrentSize ); + + info.m_nOverheadSize += (nActualSize - nLogicalSize); + if (info.m_nOverheadSize > info.m_nPeakOverheadSize) + { + info.m_nPeakOverheadSize = info.m_nOverheadSize; + } + + info.m_nTime += nTime; +} + +void CDbgMemAlloc::RegisterDeallocation( MemInfo_t &info, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) +{ + --info.m_nCurrentCount; + info.m_nCurrentSize -= nLogicalSize; + + for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) + { + if (nLogicalSize <= s_pCountSizes[i]) + { + --info.m_pCount[i]; + break; + } + } + + if ( nLogicalSize > g_TargetCountRangeMin && nLogicalSize <= g_TargetCountRangeMax ) + { + info.m_nCurTargetRange--; + } + + Assert( info.m_nPeakCount >= info.m_nCurrentCount ); + Assert( info.m_nPeakSize >= info.m_nCurrentSize ); + Assert( info.m_nCurrentCount >= 0 ); + Assert( info.m_nCurrentSize >= 0 ); + + info.m_nOverheadSize -= (nActualSize - nLogicalSize); + + info.m_nTime += nTime; +} + + +//----------------------------------------------------------------------------- +// Gets the allocation file name +//----------------------------------------------------------------------------- + +const char *CDbgMemAlloc::GetAllocatonFileName( void *pMem ) +{ + if (!pMem) + return ""; + + CrtDbgMemHeader_t *pHeader = GetCrtDbgMemHeader( pMem ); + if ( pHeader->m_pFileName ) + return pHeader->m_pFileName; + else + return g_pszUnknown; +} + +//----------------------------------------------------------------------------- +// Gets the allocation file name +//----------------------------------------------------------------------------- +int CDbgMemAlloc::GetAllocatonLineNumber( void *pMem ) +{ + if ( !pMem ) + return 0; + + CrtDbgMemHeader_t *pHeader = GetCrtDbgMemHeader( pMem ); + return pHeader->m_nLineNumber; +} + +//----------------------------------------------------------------------------- +// Debug versions of the main allocation methods +//----------------------------------------------------------------------------- +void *CDbgMemAlloc::Alloc( size_t nSize, const char *pFileName, int nLine ) +{ + HEAP_LOCK(); + +#if defined( USE_STACK_TRACES ) + unsigned int iStatEntryIndex = m_CallStackStats.GetEntryIndex( CCallStackStorage( m_CallStackStats.StackFunction, 1 ) ); +#endif + + if ( !m_bInitialized ) + { + void *pRetval = InternalMalloc( nSize, pFileName, nLine ); + +#if defined( USE_STACK_TRACES ) + if( pRetval ) + { + GetAllocationStatIndex_Internal( pRetval ) = iStatEntryIndex; + } +#endif + + return pRetval; + } + + + + if ( pFileName != g_pszUnknown ) + pFileName = FindOrCreateFilename( pFileName ); + + GetActualDbgInfo( pFileName, nLine ); + + /* + if ( strcmp( pFileName, "class CUtlVector<int,class CUtlMemory<int> >" ) == 0) + { + GetActualDbgInfo( pFileName, nLine ); + } + */ + + m_Timer.Start(); + void *pMem = InternalMalloc( nSize, pFileName, nLine ); + m_Timer.End(); + +#if defined( USE_STACK_TRACES ) + if( pMem ) + { + GetAllocationStatIndex_Internal( pMem ) = iStatEntryIndex; + } +#endif + + ApplyMemoryInitializations( pMem, nSize ); + +#if defined( USE_STACK_TRACES ) + RegisterAllocation( GetAllocationStatIndex_Internal( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); +#else + RegisterAllocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); +#endif + + if ( !pMem ) + { + SetCRTAllocFailed( nSize ); + } + return pMem; +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +void *CDbgMemAlloc::AllocAlign( size_t nSize, size_t align, const char *pFileName, int nLine ) +{ + HEAP_LOCK(); + +#if defined( USE_STACK_TRACES ) + unsigned int iStatEntryIndex = m_CallStackStats.GetEntryIndexForCurrentCallStack( 1 ); +#endif + + if ( !m_bInitialized ) + { + void *pRetval = InternalMalloc( nSize, pFileName, nLine ); + +#if defined( USE_STACK_TRACES ) + if( pRetval ) + { + GetAllocationStatIndex_Internal( pRetval ) = iStatEntryIndex; + } +#endif + + return pRetval; + } + + + + if ( pFileName != g_pszUnknown ) + pFileName = FindOrCreateFilename( pFileName ); + + GetActualDbgInfo( pFileName, nLine ); + + /* + if ( strcmp( pFileName, "class CUtlVector<int,class CUtlMemory<int> >" ) == 0) + { + GetActualDbgInfo( pFileName, nLine ); + } + */ + + m_Timer.Start(); + void *pMem = InternalMallocAligned( nSize, align, pFileName, nLine ); + m_Timer.End(); + +#if defined( USE_STACK_TRACES ) + if( pMem ) + { + GetAllocationStatIndex_Internal( pMem ) = iStatEntryIndex; + } +#endif + + ApplyMemoryInitializations( pMem, nSize ); + +#if defined( USE_STACK_TRACES ) + RegisterAllocation( GetAllocationStatIndex_Internal( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); +#else + RegisterAllocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); +#endif + + if ( !pMem ) + { + SetCRTAllocFailed( nSize ); + } + return pMem; +} +#endif + +void *CDbgMemAlloc::Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + HEAP_LOCK(); + + pFileName = FindOrCreateFilename( pFileName ); + +#if defined( USE_STACK_TRACES ) + unsigned int iStatEntryIndex = m_CallStackStats.GetEntryIndex( CCallStackStorage( m_CallStackStats.StackFunction, 1 ) ); +#endif + + if ( !m_bInitialized ) + { + pMem = InternalRealloc( pMem, nSize, pFileName, nLine ); + +#if defined( USE_STACK_TRACES ) + if( pMem ) + { + GetAllocationStatIndex_Internal( pMem ) = iStatEntryIndex; + } +#endif + return pMem; + } + + if ( pMem != 0 ) + { +#if defined( USE_STACK_TRACES ) + RegisterDeallocation( GetAllocationStatIndex_Internal( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), 0 ); +#else + RegisterDeallocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), 0 ); +#endif + } + + GetActualDbgInfo( pFileName, nLine ); + + m_Timer.Start(); + pMem = InternalRealloc( pMem, nSize, pFileName, nLine ); + m_Timer.End(); + +#if defined( USE_STACK_TRACES ) + if( pMem ) + { + GetAllocationStatIndex_Internal( pMem ) = iStatEntryIndex; + } +#endif + +#if defined( USE_STACK_TRACES ) + RegisterAllocation( GetAllocationStatIndex_Internal( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); +#else + RegisterAllocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); +#endif + + if ( !pMem ) + { + SetCRTAllocFailed( nSize ); + } + return pMem; +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +void *CDbgMemAlloc::ReallocAlign( void *pMem, size_t nSize, size_t align ) +{ +/* + // NOTE: Uncomment this to find unknown allocations + const char *pFileName = g_pszUnknown; + int nLine; + GetActualDbgInfo( pFileName, nLine ); + if (pFileName == g_pszUnknown) + { + int x = 3; + } +*/ + char szModule[MAX_PATH]; + if ( GetCallerModule( szModule, MAX_PATH ) ) + { + return ReallocAlign( pMem, nSize, align, szModule, 0 ); + } + else + { + return ReallocAlign( pMem, nSize, align, g_pszUnknown, 0 ); + } +// return malloc( nSize ); +} +void *CDbgMemAlloc::ReallocAlign( void *pMem, size_t nSize, size_t align, const char *pFileName, int nLine ) +{ + HEAP_LOCK(); + + pFileName = FindOrCreateFilename( pFileName ); + +#if defined( USE_STACK_TRACES ) + unsigned int iStatEntryIndex = m_CallStackStats.GetEntryIndexForCurrentCallStack( 1 ); +#endif + + if ( !m_bInitialized ) + { + pMem = InternalReallocAligned( pMem, nSize, align, pFileName, nLine ); + +#if defined( USE_STACK_TRACES ) + if( pMem ) + { + GetAllocationStatIndex_Internal( pMem ) = iStatEntryIndex; + } +#endif + return pMem; + } + + if ( pMem != 0 ) + { +#if defined( USE_STACK_TRACES ) + RegisterDeallocation( GetAllocationStatIndex_Internal( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), 0 ); +#else + RegisterDeallocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), 0 ); +#endif + } + + GetActualDbgInfo( pFileName, nLine ); + + m_Timer.Start(); + pMem = InternalReallocAligned( pMem, nSize, align, pFileName, nLine ); + m_Timer.End(); + +#if defined( USE_STACK_TRACES ) + if( pMem ) + { + GetAllocationStatIndex_Internal( pMem ) = iStatEntryIndex; + } +#endif + +#if defined( USE_STACK_TRACES ) + RegisterAllocation( GetAllocationStatIndex_Internal( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); +#else + RegisterAllocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); +#endif + + if ( !pMem ) + { + SetCRTAllocFailed( nSize ); + } + return pMem; +} +#endif + +void CDbgMemAlloc::Free( void *pMem, const char * /*pFileName*/, int nLine ) +{ + if ( !pMem ) + return; + + HEAP_LOCK(); + + if ( !m_bInitialized ) + { + InternalFree( pMem ); + return; + } + + size_t nOldLogicalSize = InternalLogicalSize( pMem ); + size_t nOldSize = InternalMSize( pMem ); + +#if defined( USE_STACK_TRACES ) + unsigned int oldStatIndex = GetAllocationStatIndex_Internal( pMem ); +#else + const char *pOldFileName = GetAllocatonFileName( pMem ); + int oldLine = GetAllocatonLineNumber( pMem ); +#endif + + + m_Timer.Start(); + InternalFree( pMem ); + m_Timer.End(); + +#if defined( USE_STACK_TRACES ) + RegisterDeallocation( oldStatIndex, nOldLogicalSize, nOldSize, m_Timer.GetDuration().GetMicroseconds() ); +#else + RegisterDeallocation( pOldFileName, oldLine, nOldLogicalSize, nOldSize, m_Timer.GetDuration().GetMicroseconds() ); +#endif +} + +void *CDbgMemAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + return NULL; +} + + +//----------------------------------------------------------------------------- +// Returns the size of a particular allocation (NOTE: may be larger than the size requested!) +//----------------------------------------------------------------------------- +size_t CDbgMemAlloc::GetSize( void *pMem ) +{ + HEAP_LOCK(); + + if ( !pMem ) + return m_GlobalInfo.m_nCurrentSize; + + return InternalMSize( pMem ); +} + + +//----------------------------------------------------------------------------- +// FIXME: Remove when we make our own heap! Crt stuff we're currently using +//----------------------------------------------------------------------------- +int32 CDbgMemAlloc::CrtSetBreakAlloc( int32 lNewBreakAlloc ) +{ +#ifdef POSIX + return 0; +#else + return _CrtSetBreakAlloc( lNewBreakAlloc ); +#endif +} + +int CDbgMemAlloc::CrtSetReportMode( int nReportType, int nReportMode ) +{ +#ifdef POSIX + return 0; +#else + return _CrtSetReportMode( nReportType, nReportMode ); +#endif +} + +int CDbgMemAlloc::CrtIsValidHeapPointer( const void *pMem ) +{ +#ifdef POSIX + return 0; +#else + return _CrtIsValidHeapPointer( pMem ); +#endif +} + +int CDbgMemAlloc::CrtIsValidPointer( const void *pMem, unsigned int size, int access ) +{ +#ifdef POSIX + return 0; +#else + return _CrtIsValidPointer( pMem, size, access ); +#endif +} + +#define DBGMEM_CHECKMEMORY 1 + +int CDbgMemAlloc::CrtCheckMemory( void ) +{ +#if !defined( DBGMEM_CHECKMEMORY ) || defined( POSIX ) + return 1; +#elif defined( _WIN32 ) + if ( !_CrtCheckMemory()) + { + Msg( "Memory check failed!\n" ); + return 0; + } + return 1; +#else + return 1; +#endif +} + +int CDbgMemAlloc::CrtSetDbgFlag( int nNewFlag ) +{ +#ifdef POSIX + return 0; +#else + return _CrtSetDbgFlag( nNewFlag ); +#endif +} + +void CDbgMemAlloc::CrtMemCheckpoint( _CrtMemState *pState ) +{ +#ifndef POSIX + _CrtMemCheckpoint( pState ); +#endif +} + +// FIXME: Remove when we have our own allocator +void* CDbgMemAlloc::CrtSetReportFile( int nRptType, void* hFile ) +{ +#ifdef POSIX + return 0; +#else + return (void*)_CrtSetReportFile( nRptType, (_HFILE)hFile ); +#endif +} + +void* CDbgMemAlloc::CrtSetReportHook( void* pfnNewHook ) +{ +#ifdef POSIX + return 0; +#else + return (void*)_CrtSetReportHook( (_CRT_REPORT_HOOK)pfnNewHook ); +#endif +} + +int CDbgMemAlloc::CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * pMsg ) +{ +#ifdef POSIX + return 0; +#else + return _CrtDbgReport( nRptType, szFile, nLine, szModule, pMsg ); +#endif +} + +int CDbgMemAlloc::heapchk() +{ +#ifdef POSIX + return 0; +#else + if ( CrtCheckMemory() ) + return _HEAPOK; + else + return _HEAPBADPTR; +#endif +} + +void CDbgMemAlloc::DumpBlockStats( void *p ) +{ + DbgMemHeader_t *pBlock = (DbgMemHeader_t *)p - 1; + if ( !CrtIsValidHeapPointer( pBlock ) ) + { + Msg( "0x%x is not valid heap pointer\n", p ); + return; + } + + const char *pFileName = GetAllocatonFileName( p ); + int line = GetAllocatonLineNumber( p ); + + Msg( "0x%x allocated by %s line %d, %d bytes\n", p, pFileName, line, GetSize( p ) ); +} + +//----------------------------------------------------------------------------- +// Stat output +//----------------------------------------------------------------------------- +void CDbgMemAlloc::DumpMemInfo( const char *pAllocationName, int line, const MemInfo_t &info ) +{ + m_OutputFunc("%s, line %i\t%.1f\t%.1f\t%.1f\t%.1f\t%.1f\t%d\t%d\t%d\t%d\t%d\t%d\t%d", + pAllocationName, + line, + info.m_nCurrentSize / 1024.0f, + info.m_nPeakSize / 1024.0f, + info.m_nTotalSize / 1024.0f, + info.m_nOverheadSize / 1024.0f, + info.m_nPeakOverheadSize / 1024.0f, + (int)(info.m_nTime / 1000), + info.m_nCurrentCount, + info.m_nPeakCount, + info.m_nTotalCount, + info.m_nSumTargetRange, + info.m_nCurTargetRange, + info.m_nMaxTargetRange + ); + + for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) + { + m_OutputFunc( "\t%d", info.m_pCount[i] ); + } + + m_OutputFunc("\n"); +} + + +//----------------------------------------------------------------------------- +// Stat output +//----------------------------------------------------------------------------- +size_t CDbgMemAlloc::ComputeMemoryUsedBy( char const *pchSubStr) +{ + size_t total = 0; + StatMapIter_FileLine_t iter = m_StatMap_FileLine.begin(); + while(iter != m_StatMap_FileLine.end()) + { + if(!pchSubStr || strstr(iter->first.m_pFileName,pchSubStr)) + { + total += iter->second.m_nCurrentSize; + } + iter++; + } + return total; +} + +void CDbgMemAlloc::DumpFileStats() +{ + StatMapIter_FileLine_t iter = m_StatMap_FileLine.begin(); + while(iter != m_StatMap_FileLine.end()) + { + DumpMemInfo( iter->first.m_pFileName, iter->first.m_nLine, iter->second ); + iter++; + } +} + +void CDbgMemAlloc::DumpStatsFileBase( char const *pchFileBase ) +{ + char szFileName[MAX_PATH]; + static int s_FileCount = 0; + if (m_OutputFunc == DefaultHeapReportFunc) + { + char *pPath = ""; +#ifdef _X360 + pPath = "D:\\"; +#elif defined( _PS3 ) + pPath = "/app_home/"; +#endif + + +#if defined( _MEMTEST ) && defined( _WIN32 ) + char szXboxName[32]; + strcpy( szXboxName, "xbox" ); + DWORD numChars = sizeof( szXboxName ); + DmGetXboxName( szXboxName, &numChars ); + char *pXboxName = strstr( szXboxName, "_360" ); + if ( pXboxName ) + { + *pXboxName = '\0'; + } + + SYSTEMTIME systemTime; + GetLocalTime( &systemTime ); + //_snprintf( szFileName, sizeof( szFileName ), "%s%s_%2.2d%2.2d_%2.2d%2.2d%2.2d_%d.txt", pPath, s_szStatsMapName, systemTime.wMonth, systemTime.wDay, systemTime.wHour, systemTime.wMinute, systemTime.wSecond, s_FileCount ); + _snprintf( szFileName, sizeof( szFileName ), "%s%s_%d.txt", pPath, s_szStatsMapName, s_FileCount ); +#else + _snprintf( szFileName, sizeof( szFileName ), "%s%s_%d.txt", pPath, pchFileBase, s_FileCount ); +#endif + + ++s_FileCount; + + s_DbgFile = fopen(szFileName, "wt"); + if (!s_DbgFile) + return; + } + + { + HEAP_LOCK(); + + m_OutputFunc("Allocation type\tCurrent Size(k)\tPeak Size(k)\tTotal Allocations(k)\tOverhead Size(k)\tPeak Overhead Size(k)\tTime(ms)\tCurrent Count\tPeak Count\tTotal Count\tTNum\tTCur\tTMax"); + + for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) + { + m_OutputFunc( "\t%s", s_pCountHeader[i] ); + } + + m_OutputFunc("\n"); + + MemInfo_t totals = m_GlobalInfo; +#ifdef _PS3 + { + // Add a line for system heap stats + static malloc_managed_size mms; + (g_pMemOverrideRawCrtFns->pfn_malloc_stats)( &mms ); + + MemInfo_t info; + info.m_nCurrentSize = mms.current_inuse_size; + info.m_nPeakSize = mms.max_system_size; + info.m_nOverheadSize = mms.current_system_size - mms.current_inuse_size; + DumpMemInfo( "||PS3 malloc_stats||", 0, info ); + + // Add a line for PRXs + char prxFilename[256]; + sys_prx_id_t prxIDs[256]; + sys_prx_segment_info_t prxSegments[32]; + sys_prx_get_module_list_t prxList = { sizeof( sys_prx_get_module_list_t ), ARRAYSIZE( prxIDs ), 0, prxIDs, NULL }; + sys_prx_get_module_list( 0, &prxList ); + Assert( prxList.count < ARRAYSIZE( prxIDs ) ); + memset( &info, 0, sizeof( info ) ); + for ( int i = 0; i < prxList.count; i++ ) + { + sys_prx_module_info_t prxInfo; + prxInfo.size = sizeof( sys_prx_module_info_t ); + prxInfo.filename = prxFilename; + prxInfo.filename_size = sizeof( prxFilename ); + prxInfo.segments = prxSegments; + prxInfo.segments_num = ARRAYSIZE( prxSegments ); + sys_prx_get_module_info( prxList.idlist[i], 0, &prxInfo ); + Assert( prxInfo.segments_num < ARRAYSIZE( prxSegments ) ); + for ( int j = 0; j < prxInfo.segments_num; j++ ) + { + info.m_nCurrentSize += prxInfo.segments[j].memsz; + } + } + DumpMemInfo( "PS3 PRXs", 0, info ); + + // Add PRX sizes to our global tracked total: + totals.m_nCurrentSize += info.m_nCurrentSize; + } +#endif // _PS3 + + // The total of all memory usage we know about: + DumpMemInfo( "||Totals||", 0, totals ); + + if ( IsGameConsole() ) + { + // Add a line showing total system memory usage from the OS (if this is more than + // "||Totals||", then there is unknown memory usage that we need to track down): + size_t usedMemory, freeMemory; + GlobalMemoryStatus( &usedMemory, &freeMemory ); + MemInfo_t info; + info.m_nCurrentSize = usedMemory; + DumpMemInfo( "||Used Memory||", 0, info ); + } + +#ifdef _MEMTEST + { + // Add lines for GPU allocations + int nGPUMemSize, nGPUMemFree, nTextureSize, nRTSize, nVBSize, nIBSize, nUnknown; + if ( 7 == sscanf( s_szStatsComment, "%d %d %d %d %d %d %d", &nGPUMemSize, &nGPUMemFree, &nTextureSize, &nRTSize, &nVBSize, &nIBSize, &nUnknown ) ) + { + int nTotalUsed = nTextureSize + nRTSize + nVBSize + nIBSize + nUnknown; + int nOverhead = ( nGPUMemSize - nTotalUsed ) - nGPUMemFree; + m_OutputFunc( "||PS3 RSX: total used||, line 0\t%.1f\n", nTotalUsed / 1024.0f ); + m_OutputFunc( "PS3 RSX: textures, line 0\t%.1f\n", nTextureSize / 1024.0f ); + m_OutputFunc( "PS3 RSX: render targets, line 0\t%.1f\n", nRTSize / 1024.0f ); + m_OutputFunc( "PS3 RSX: vertex buffers, line 0\t%.1f\n", nVBSize / 1024.0f ); + m_OutputFunc( "PS3 RSX: index buffers, line 0\t%.1f\n", nIBSize / 1024.0f ); + m_OutputFunc( "PS3 RSX: unknown, line 0\t%.1f\n", nUnknown / 1024.0f ); + m_OutputFunc( "PS3 RSX: overhead, line 0\t%.1f\n", nOverhead / 1024.0f ); + } + } +#endif + + //m_OutputFunc("File/Line Based\n"); + DumpFileStats(); + } + + if (m_OutputFunc == DefaultHeapReportFunc) + { + fclose(s_DbgFile); + +#if defined( _X360 ) + XBX_rMemDump( szFileName ); +#endif + } +} + +void CDbgMemAlloc::GlobalMemoryStatus( size_t *pUsedMemory, size_t *pFreeMemory ) +{ + if ( !pUsedMemory || !pFreeMemory ) + return; + +#if defined ( _X360 ) + + // GlobalMemoryStatus tells us how much physical memory is free + MEMORYSTATUS stat; + ::GlobalMemoryStatus( &stat ); + *pFreeMemory = stat.dwAvailPhys; + + // Used is total minus free (discount the 32MB system reservation) + *pUsedMemory = ( stat.dwTotalPhys - 32*1024*1024 ) - *pFreeMemory; + +#elif defined( _PS3 ) + + // need to factor in how much empty space there is in the heap + // (since it NEVER returns pages back to the OS after hitting a high-watermark) + static malloc_managed_size mms; + (g_pMemOverrideRawCrtFns->pfn_malloc_stats)( &mms ); + int heapFree = mms.current_system_size - mms.current_inuse_size; + Assert( heapFree >= 0 ); + + // sys_memory_get_user_memory_size tells us how much PPU memory is used/free + static sys_memory_info stat; + sys_memory_get_user_memory_size( &stat ); + *pFreeMemory = stat.available_user_memory; + *pFreeMemory += heapFree; + *pUsedMemory = stat.total_user_memory - *pFreeMemory; + // 213MB are available in retail mode, so adjust free mem to reflect that even if we're in devkit mode + const size_t RETAIL_SIZE = 213*1024*1024; + if ( stat.total_user_memory > RETAIL_SIZE ) + *pFreeMemory -= stat.total_user_memory - RETAIL_SIZE; + +#else + + // no data + *pFreeMemory = 0; + *pUsedMemory = 0; + +#endif +} + +#ifdef USE_STACK_TRACES +void CDbgMemAlloc::DumpCallStackFlow( char const *pchFileBase ) +{ + HEAP_LOCK(); + + char szFileName[MAX_PATH]; + static int s_FileCount = 0; + + char *pPath = ""; + if ( IsX360() ) + { + pPath = "D:\\"; + } + +#if defined( _MEMTEST ) && defined( _WIN32 ) + char szXboxName[32]; + strcpy( szXboxName, "xbox" ); + DWORD numChars = sizeof( szXboxName ); + DmGetXboxName( szXboxName, &numChars ); + char *pXboxName = strstr( szXboxName, "_360" ); + if ( pXboxName ) + { + *pXboxName = '\0'; + } + + SYSTEMTIME systemTime; + GetLocalTime( &systemTime ); + _snprintf( szFileName, sizeof( szFileName ), "%s%s_%2.2d%2.2d_%2.2d%2.2d%2.2d_%d.csf", pPath, s_szStatsMapName, systemTime.wMonth, systemTime.wDay, systemTime.wHour, systemTime.wMinute, systemTime.wSecond, s_FileCount ); +#else + _snprintf( szFileName, sizeof( szFileName ), "%s%s%d.vcsf", pPath, pchFileBase, s_FileCount ); +#endif + + ++s_FileCount; + m_CallStackStats.DumpToFile( szFileName, false ); +} +#endif + +//----------------------------------------------------------------------------- +// Stat output +//----------------------------------------------------------------------------- +void CDbgMemAlloc::DumpStats() +{ + DumpStatsFileBase( "memstats" ); +#ifdef USE_STACK_TRACES + DumpCallStackFlow( "memflow" ); +#endif +} + +void CDbgMemAlloc::SetCRTAllocFailed( size_t nSize ) +{ + m_sMemoryAllocFailed = nSize; + DebuggerBreakIfDebugging(); + char buffer[256]; + _snprintf( buffer, sizeof( buffer ), "***** OUT OF MEMORY! attempted allocation size: %u ****\n", nSize ); +#if defined( _PS3 ) && defined( _DEBUG ) + DebuggerBreak(); +#endif // _PS3 + +#ifdef _X360 + XBX_OutputDebugString( buffer ); + if ( !Plat_IsInDebugSession() ) + { + XBX_CrashDump( true ); +#if defined( _DEMO ) + XLaunchNewImage( XLAUNCH_KEYWORD_DEFAULT_APP, 0 ); +#else + XLaunchNewImage( "default.xex", 0 ); +#endif + } +#elif defined(_WIN32 ) + OutputDebugString( buffer ); + if ( !Plat_IsInDebugSession() ) + { + AssertFatalMsg( false, buffer ); + abort(); + } +#else + printf( "%s\n", buffer ); + if ( !Plat_IsInDebugSession() ) + { + AssertFatalMsg( false, buffer ); + exit( 0 ); + } +#endif +} + +size_t CDbgMemAlloc::MemoryAllocFailed() +{ + return m_sMemoryAllocFailed; +} + + + +#ifdef LINUX +// +// Under linux we can ask GLIBC to override malloc for us +// Base on code from Ryan, http://hg.icculus.org/icculus/mallocmonitor/file/29c4b0d049f7/monitor_client/malloc_hook_glibc.c +// +// +static void *glibc_malloc_hook = NULL; +static void *glibc_realloc_hook = NULL; +static void *glibc_memalign_hook = NULL; +static void *glibc_free_hook = NULL; + +/* convenience functions for setting the hooks... */ +static inline void save_glibc_hooks(void); +static inline void set_glibc_hooks(void); +static inline void set_override_hooks(void); + +CThreadMutex g_HookMutex; +/* + * Our overriding hooks...they call through to the original C runtime + * implementations and report to the monitoring daemon. + */ + +static void *override_malloc_hook(size_t s, const void *caller) +{ + void *retval; + AUTO_LOCK( g_HookMutex ); + set_glibc_hooks(); /* put glibc back in control. */ + retval = InternalMalloc( s, NULL, 0 ); + save_glibc_hooks(); /* update in case glibc changed them. */ + + set_override_hooks(); /* only restore hooks if daemon is listening */ + + return(retval); +} /* override_malloc_hook */ + + +static void *override_realloc_hook(void *ptr, size_t s, const void *caller) +{ + void *retval; + AUTO_LOCK( g_HookMutex ); + + set_glibc_hooks(); /* put glibc back in control. */ + retval = InternalRealloc(ptr, s, NULL, 0); /* call glibc version. */ + save_glibc_hooks(); /* update in case glibc changed them. */ + + set_override_hooks(); /* only restore hooks if daemon is listening */ + + return(retval); +} /* override_realloc_hook */ + + +static void *override_memalign_hook(size_t a, size_t s, const void *caller) +{ + void *retval; + AUTO_LOCK( g_HookMutex ); + + set_glibc_hooks(); /* put glibc back in control. */ + retval = memalign(a, s); /* call glibc version. */ + save_glibc_hooks(); /* update in case glibc changed them. */ + + set_override_hooks(); /* only restore hooks if daemon is listening */ + + return(retval); +} /* override_memalign_hook */ + + +static void override_free_hook(void *ptr, const void *caller) +{ + AUTO_LOCK( g_HookMutex ); + + set_glibc_hooks(); /* put glibc back in control. */ + InternalFree(ptr); /* call glibc version. */ + save_glibc_hooks(); /* update in case glibc changed them. */ + + set_override_hooks(); /* only restore hooks if daemon is listening */ +} /* override_free_hook */ + + + +/* + * Convenience functions for swapping the hooks around... + */ + +/* + * Save a copy of the original allocation hooks, so we can call into them + * from our overriding functions. It's possible that glibc might change + * these hooks under various conditions (so the manual's examples seem + * to suggest), so we update them whenever we finish calling into the + * the originals. + */ +static inline void save_glibc_hooks(void) +{ + glibc_malloc_hook = (void *)__malloc_hook; + glibc_realloc_hook = (void *)__realloc_hook; + glibc_memalign_hook = (void *)__memalign_hook; + glibc_free_hook = (void *)__free_hook; +} /* save_glibc_hooks */ + +/* + * Restore the hooks to the glibc versions. This is needed since, say, + * their realloc() might call malloc() or free() under the hood, etc, so + * it's safer to let them have complete control over the subsystem, which + * also makes our logging saner, too. + */ +static inline void set_glibc_hooks(void) +{ + __malloc_hook = (void* (*)(size_t, const void*))glibc_malloc_hook; + __realloc_hook = (void* (*)(void*, size_t, const void*))glibc_realloc_hook; + __memalign_hook = (void* (*)(size_t, size_t, const void*))glibc_memalign_hook; + __free_hook = (void (*)(void*, const void*))glibc_free_hook; +} /* set_glibc_hooks */ + + +/* + * Put our hooks back in place. This should be done after the original + * glibc version has been called and we've finished any logging (which + * may call glibc functions, too). This sets us up for the next calls from + * the application. + */ +static inline void set_override_hooks(void) +{ + __malloc_hook = override_malloc_hook; + __realloc_hook = override_realloc_hook; + __memalign_hook = override_memalign_hook; + __free_hook = override_free_hook; +} /* set_override_hooks */ + + + +/* + * The Hook Of All Hooks...how we get in there in the first place. + */ + +/* + * glibc will call this when the malloc subsystem is initializing, giving + * us a chance to install hooks that override the functions. + */ +static void override_init_hook(void) +{ + AUTO_LOCK( g_HookMutex ); + + /* install our hooks. Will connect to daemon on first malloc, etc. */ + save_glibc_hooks(); + set_override_hooks(); +} /* override_init_hook */ + + +/* + * __malloc_initialize_hook is apparently a "weak variable", so you can + * define and assign it here even though it's in glibc, too. This lets + * us hook into malloc as soon as the runtime initializes, and before + * main() is called. Basically, this whole trick depends on this. + */ +void (*__malloc_initialize_hook)(void) __attribute__((visibility("default")))= override_init_hook; + +#elif defined( OSX ) +// +// pointers to the osx versions of these functions +static void *osx_malloc_hook = NULL; +static void *osx_realloc_hook = NULL; +static void *osx_free_hook = NULL; + +// convenience functions for setting the hooks... +static inline void save_osx_hooks(void); +static inline void set_osx_hooks(void); +static inline void set_override_hooks(void); + +CThreadMutex g_HookMutex; +// +// Our overriding hooks...they call through to the original C runtime +// implementations and report to the monitoring daemon. +// + +static void *override_malloc_hook(struct _malloc_zone_t *zone, size_t s) +{ + void *retval; + set_osx_hooks(); + retval = InternalMalloc( s, NULL, 0 ); + set_override_hooks(); + + return(retval); +} + + +static void *override_realloc_hook(struct _malloc_zone_t *zone, void *ptr, size_t s) +{ + void *retval; + + set_osx_hooks(); + retval = InternalRealloc(ptr, s, NULL, 0); + set_override_hooks(); + + return(retval); +} + + +static void override_free_hook(struct _malloc_zone_t *zone, void *ptr) +{ + // sometime they pass in a null pointer from higher level calls, just ignore it + if ( !ptr ) + return; + + set_osx_hooks(); + + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( ptr ); + if ( *((int*)pInternalMem->m_Reserved) == 0xf00df00d ) + { + InternalFree( ptr ); + } + + set_override_hooks(); +} + + +/* + + These are func's we could optionally override right now on OSX but don't need to + + static size_t override_size_hook(struct _malloc_zone_t *zone, const void *ptr) + { + set_osx_hooks(); + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( (void *)ptr ); + set_override_hooks(); + if ( *((int*)pInternalMem->m_Reserved) == 0xf00df00d ) + { + return pInternalMem->nLogicalSize; + } + return 0; + } + + + static void *override_calloc_hook(struct _malloc_zone_t *zone, size_t num_items, size_t size ) + { + void *ans = override_malloc_hook( zone, num_items*size ); + if ( !ans ) + return 0; + memset( ans, 0x0, num_items*size ); + return ans; + } + + static void *override_valloc_hook(struct _malloc_zone_t *zone, size_t size ) + { + return override_calloc_hook( zone, 1, size ); + } + + static void override_destroy_hook(struct _malloc_zone_t *zone) + { + } + */ + + + +// +// Save a copy of the original allocation hooks, so we can call into them +// from our overriding functions. It's possible that osx might change +// these hooks under various conditions (so the manual's examples seem +// to suggest), so we update them whenever we finish calling into the +// the originals. +// +static inline void save_osx_hooks(void) +{ + malloc_zone_t *malloc_zone = malloc_default_zone(); + + osx_malloc_hook = (void *)malloc_zone->malloc; + osx_realloc_hook = (void *)malloc_zone->realloc; + osx_free_hook = (void *)malloc_zone->free; + + // These are func's we could optionally override right now on OSX but don't need to + // osx_size_hook = (void *)malloc_zone->size; + // osx_calloc_hook = (void *)malloc_zone->calloc; + // osx_valloc_hook = (void *)malloc_zone->valloc; + // osx_destroy_hook = (void *)malloc_zone->destroy; +} + +// +// Restore the hooks to the osx versions. This is needed since, say, +// their realloc() might call malloc() or free() under the hood, etc, so +// it's safer to let them have complete control over the subsystem, which +// also makes our logging saner, too. +// +static inline void set_osx_hooks(void) +{ + malloc_zone_t *malloc_zone = malloc_default_zone(); + malloc_zone->malloc = (void* (*)(_malloc_zone_t*, size_t))osx_malloc_hook; + malloc_zone->realloc = (void* (*)(_malloc_zone_t*, void*, size_t))osx_realloc_hook; + malloc_zone->free = (void (*)(_malloc_zone_t*, void*))osx_free_hook; + + // These are func's we could optionally override right now on OSX but don't need to + + //malloc_zone->size = (size_t (*)(_malloc_zone_t*, const void *))osx_size_hook; + //malloc_zone->calloc = (void* (*)(_malloc_zone_t*, size_t, size_t))osx_calloc_hook; + //malloc_zone->valloc = (void* (*)(_malloc_zone_t*, size_t))osx_valloc_hook; + //malloc_zone->destroy = (void (*)(_malloc_zone_t*))osx_destroy_hook; +} + + +/* + * Put our hooks back in place. This should be done after the original + * osx version has been called and we've finished any logging (which + * may call osx functions, too). This sets us up for the next calls from + * the application. + */ +static inline void set_override_hooks(void) +{ + malloc_zone_t *malloc_zone = malloc_default_zone(); + + malloc_zone->malloc = override_malloc_hook; + malloc_zone->realloc = override_realloc_hook; + malloc_zone->free = override_free_hook; + + // These are func's we could optionally override right now on OSX but don't need to + //malloc_zone->size = override_size_hook; + //malloc_zone->calloc = override_calloc_hook; + // malloc_zone->valloc = override_valloc_hook; + //malloc_zone->destroy = override_destroy_hook; +} + + +// +// The Hook Of All Hooks...how we get in there in the first place. +// +// osx will call this when the malloc subsystem is initializing, giving +// us a chance to install hooks that override the functions. +// + +void __attribute__ ((constructor)) mem_init(void) +{ + AUTO_LOCK( g_HookMutex ); + save_osx_hooks(); + set_override_hooks(); +} + +void *operator new( size_t nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + set_osx_hooks(); + void *pMem = g_pMemAlloc->Alloc(nSize, pFileName, nLine); + set_override_hooks(); + return pMem; +} + +void *operator new[] ( size_t nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + set_osx_hooks(); + void *pMem = g_pMemAlloc->Alloc(nSize, pFileName, nLine); + set_override_hooks(); + return pMem; +} + +#endif // OSX + +int GetAllocationCallStack( void *mem, void **pCallStackOut, int iMaxEntriesOut ) +{ +#if defined( USE_MEM_DEBUG ) && (defined( USE_STACK_TRACES )) + return s_DbgMemAlloc.GetCallStackForIndex( GetAllocationStatIndex_Internal( mem ), pCallStackOut, iMaxEntriesOut ); +#else + return 0; +#endif +} + + +#endif // MEM_IMPL_TYPE_DBG + +#endif // !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) diff --git a/external/vpc/tier0/memstd.cpp b/external/vpc/tier0/memstd.cpp new file mode 100644 index 0000000..20a8aec --- /dev/null +++ b/external/vpc/tier0/memstd.cpp @@ -0,0 +1,2493 @@ +//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Memory allocation! +// +// $NoKeywords: $ +//=============================================================================// + +#include "tier0/platform.h" + + +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + + +//#include <malloc.h> + +#include <algorithm> + +#include "tier0/dbg.h" +#include "tier0/memalloc.h" +#include "tier0/threadtools.h" +#include "mem_helpers.h" +#include "memstd.h" +#include "tier0/stacktools.h" +#include "tier0/minidump.h" +#ifdef _X360 +#include "xbox/xbox_console.h" +#endif + +#ifdef _PS3 +#include "memoverride_ps3.h" +#endif + +#ifndef _WIN32 +#define IsDebuggerPresent() false +#endif + +#ifdef USE_LIGHT_MEM_DEBUG +#undef USE_MEM_DEBUG +#pragma message("*** USE_LIGHT_MEM_DEBUG is ON ***") +#pragma optimize( "", off ) +#endif + +#define DEF_REGION 0 + +#if defined( _WIN32 ) || defined( _PS3 ) +#define USE_DLMALLOC +#define MEMALLOC_SEGMENT_MIXED +#define MBH_SIZE_MB ( 45 + MBYTES_STEAM_MBH_USAGE ) +//#define MEMALLOC_REGIONS +#endif // _WIN32 || _PS3 + +#ifndef USE_DLMALLOC +#ifdef _PS3 +#define malloc_internal( region, bytes ) (g_pMemOverrideRawCrtFns->pfn_malloc)(bytes) +#define malloc_aligned_internal( region, bytes, align ) (g_pMemOverrideRawCrtFns->pfn_memalign)(align, bytes) +#define realloc_internal (g_pMemOverrideRawCrtFns->pfn_realloc) +#define realloc_aligned_internal (g_pMemOverrideRawCrtFns->pfn_reallocalign) +#define free_internal (g_pMemOverrideRawCrtFns->pfn_free) +#define msize_internal (g_pMemOverrideRawCrtFns->pfn_malloc_usable_size) +#define compact_internal() (0) +#define heapstats_internal(p) (void)(0) +#else // _PS3 +#define malloc_internal( region, bytes) malloc(bytes) +#define malloc_aligned_internal( region, bytes, align ) memalign(align, bytes) +#define realloc_internal realloc +#define realloc_aligned_internal realloc +#define free_internal free +#ifdef POSIX +#define msize_internal malloc_usable_size +#else // POSIX +#define msize_internal _msize +#endif // POSIX +#define compact_internal() (0) +#define heapstats_internal(p) (void)(0) +#endif // _PS3 +#else // USE_DLMALLOC +#define MSPACES 1 +#include "dlmalloc/malloc-2.8.3.h" + +void *g_AllocRegions[] = +{ +#ifndef MEMALLOC_REGIONS +#ifdef MEMALLOC_SEGMENT_MIXED + create_mspace( 0, 1 ), // unified + create_mspace( MBH_SIZE_MB*1024*1024, 1 ), +#else + create_mspace( 100*1024*1024, 1 ), +#endif +#else // MEMALLOC_REGIONS + // @TODO: per DLL regions didn't work out very well. flux of usage left too much overhead. need to try lifetime-based management [6/9/2009 tom] + create_mspace( 82*1024*1024, 1 ), // unified +#endif // MEMALLOC_REGIONS +}; + +#ifndef MEMALLOC_REGIONS +#ifndef MEMALLOC_SEGMENT_MIXED +#define SelectRegion( region, bytes ) 0 +#else +// NOTE: this split is designed to force the 'large block' heap to ONLY perform virtual allocs (see +// DEFAULT_MMAP_THRESHOLD in malloc.cpp), to avoid ANY fragmentation or waste in an internal arena +#define REGION_SPLIT (256*1024) +#define SelectRegion( region, bytes ) g_AllocRegions[bytes < REGION_SPLIT] +#endif +#else // MEMALLOC_REGIONS +#define SelectRegion( region, bytes ) g_AllocRegions[region] +#endif // MEMALLOC_REGIONS + +#define malloc_internal( region, bytes ) mspace_malloc(SelectRegion(region,bytes), bytes) +#define malloc_aligned_internal( region, bytes, align ) mspace_memalign(SelectRegion(region,bytes), align, bytes) +FORCEINLINE void *realloc_aligned_internal( void *mem, size_t bytes, size_t align ) +{ + // TODO: implement realloc_aligned inside dlmalloc (requires splitting realloc's existing + // 'grow in-place' code into a new function, then call that w/ alloc_align/copy/free on failure) + byte *newMem = (byte *)dlrealloc( mem, bytes ); + if ( ((size_t)newMem&(align-1)) == 0 ) + return newMem; + // realloc broke alignment... + byte *fallback = (byte *)malloc_aligned_internal( DEF_REGION, bytes, align ); + if ( !fallback ) + return NULL; + memcpy( fallback, newMem, bytes ); + dlfree( newMem ); + return fallback; +} + +inline size_t compact_internal() +{ + size_t start = 0, end = 0; + + for ( int i = 0; i < ARRAYSIZE(g_AllocRegions); i++ ) + { + start += mspace_footprint( g_AllocRegions[i] ); + mspace_trim( g_AllocRegions[i], 0 ); + end += mspace_footprint( g_AllocRegions[i] ); + } + + return ( start - end ); +} + +inline void heapstats_internal( FILE *pFile ) +{ + // @TODO: improve this presentation, as a table [6/1/2009 tom] + char buf[1024]; + for ( int i = 0; i < ARRAYSIZE( g_AllocRegions ); i++ ) + { + struct mallinfo info = mspace_mallinfo( g_AllocRegions[ i ] ); + size_t footPrint = mspace_footprint( g_AllocRegions[ i ] ); + size_t maxFootPrint = mspace_max_footprint( g_AllocRegions[ i ] ); + _snprintf( buf, sizeof(buf), + "\ndlmalloc mspace %d (%s)\n" + " %d:footprint -%10d (total space used by the mspace)\n" + " %d:footprint_max -%10d (maximum total space used by the mspace)\n" + " %d:arena -%10d (non-mmapped space allocated from system)\n" + " %d:ordblks -%10d (number of free chunks)\n" + " %d:hblkhd -%10d (space in mmapped regions)\n" + " %d:usmblks -%10d (maximum total allocated space)\n" + " %d:uordblks -%10d (total allocated space)\n" + " %d:fordblks -%10d (total free space)\n" + " %d:keepcost -%10d (releasable (via malloc_trim) space)\n", + i, i?"medium-block":"large-block", i,footPrint, i,maxFootPrint, i,info.arena, i,info.ordblks, i,info.hblkhd, i,info.usmblks, i,info.uordblks, i,info.fordblks, i,info.keepcost ); + if ( pFile ) + fprintf( pFile, "%s", buf ); + else + Msg( "%s", buf ); + } +} + +#define realloc_internal dlrealloc +#define free_internal dlfree +#define msize_internal dlmalloc_usable_size +#endif // USE_DLMALLOC + +#ifdef TIME_ALLOC +CAverageCycleCounter g_MallocCounter; +CAverageCycleCounter g_ReallocCounter; +CAverageCycleCounter g_FreeCounter; + +#define PrintOne( name ) \ + Msg("%-48s: %6.4f avg (%8.1f total, %7.3f peak, %5d iters)\n", \ + #name, \ + g_##name##Counter.GetAverageMilliseconds(), \ + g_##name##Counter.GetTotalMilliseconds(), \ + g_##name##Counter.GetPeakMilliseconds(), \ + g_##name##Counter.GetIters() ); \ + memset( &g_##name##Counter, 0, sizeof(g_##name##Counter) ) + +void PrintAllocTimes() +{ + PrintOne( Malloc ); + PrintOne( Realloc ); + PrintOne( Free ); +} + +#define PROFILE_ALLOC(name) CAverageTimeMarker name##_ATM( &g_##name##Counter ) + +#else // TIME_ALLOC +#define PROFILE_ALLOC( name ) ((void)0) +#define PrintAllocTimes() ((void)0) +#endif // TIME_ALLOC + +#if _MSC_VER < 1400 && defined( MSVC ) && !defined(_STATIC_LINKED) && (defined(_DEBUG) || defined(USE_MEM_DEBUG)) +void *operator new( unsigned int nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + return ::operator new( nSize ); +} + +void *operator new[] ( unsigned int nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + return ::operator new[]( nSize ); +} +#endif + +#include "mem_impl_type.h" +#if MEM_IMPL_TYPE_STD + +//----------------------------------------------------------------------------- +// Singleton... +//----------------------------------------------------------------------------- +#pragma warning( disable:4074 ) // warning C4074: initializers put in compiler reserved initialization area +#pragma init_seg( compiler ) + +#if MEM_SBH_ENABLED +CSmallBlockPool< CStdMemAlloc::CFixedAllocator< MBYTES_PRIMARY_SBH, true> >::SharedData_t CSmallBlockPool< CStdMemAlloc::CFixedAllocator< MBYTES_PRIMARY_SBH, true> >::gm_SharedData CONSTRUCT_EARLY; +#ifdef MEMALLOC_USE_SECONDARY_SBH +CSmallBlockPool< CStdMemAlloc::CFixedAllocator< MBYTES_SECONDARY_SBH, false> >::SharedData_t CSmallBlockPool< CStdMemAlloc::CFixedAllocator< MBYTES_SECONDARY_SBH, false> >::gm_SharedData CONSTRUCT_EARLY; +#endif +#ifndef MEMALLOC_NO_FALLBACK +CSmallBlockPool< CStdMemAlloc::CVirtualAllocator >::SharedData_t CSmallBlockPool< CStdMemAlloc::CVirtualAllocator >::gm_SharedData CONSTRUCT_EARLY; +#endif +#endif // MEM_SBH_ENABLED + +static CStdMemAlloc s_StdMemAlloc CONSTRUCT_EARLY; + +#ifdef _PS3 + +MemOverrideRawCrtFunctions_t *g_pMemOverrideRawCrtFns; +IMemAlloc *g_pMemAllocInternalPS3 = &s_StdMemAlloc; +PLATFORM_OVERRIDE_MEM_ALLOC_INTERNAL_PS3_IMPL + +#else // !_PS3 + +#ifndef TIER0_VALIDATE_HEAP +IMemAlloc *g_pMemAlloc = &s_StdMemAlloc; +#else +IMemAlloc *g_pActualAlloc = &s_StdMemAlloc; +#endif + +#endif // _PS3 + +CStdMemAlloc::CStdMemAlloc() +: m_pfnFailHandler( DefaultFailHandler ), + m_sMemoryAllocFailed( (size_t)0 ), + m_bInCompact( false ) +{ +#ifdef _PS3 + g_pMemAllocInternalPS3 = &s_StdMemAlloc; + PLATFORM_OVERRIDE_MEM_ALLOC_INTERNAL_PS3.m_pMemAllocCached = &s_StdMemAlloc; + malloc_managed_size mms; + mms.current_inuse_size = 0x12345678; + mms.current_system_size = 0x09ABCDEF; + mms.max_system_size = reinterpret_cast< size_t >( this ); + int iResult = malloc_stats( &mms ); + g_pMemOverrideRawCrtFns = reinterpret_cast< MemOverrideRawCrtFunctions_t * >( iResult ); +#endif +} + +#if MEM_SBH_ENABLED +//----------------------------------------------------------------------------- +// Small block heap (multi-pool) +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template <typename T> +inline T MemAlign( T val, unsigned alignment ) +{ + return (T)( ( (unsigned)val + alignment - 1 ) & ~( alignment - 1 ) ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +template <typename CAllocator> +void CSmallBlockPool<CAllocator>::Init( unsigned nBlockSize ) +{ + SharedData_t *pSharedData = GetSharedData(); + if ( !pSharedData->m_pBase ) + { + pSharedData->m_pBase = pSharedData->m_Allocator.AllocatePoolMemory(); + pSharedData->m_pLimit = pSharedData->m_pBase + CAllocator::TOTAL_BYTES; + pSharedData->m_pNextBlock = pSharedData->m_pBase; + } + + if ( !( nBlockSize % MIN_SBH_ALIGN == 0 && nBlockSize >= MIN_SBH_BLOCK && nBlockSize >= sizeof(TSLNodeBase_t) ) ) + DebuggerBreak(); + + m_nBlockSize = nBlockSize; + m_pNextAlloc = NULL; + m_nCommittedPages = 0; +} + +template <typename CAllocator> +size_t CSmallBlockPool<CAllocator>::GetBlockSize() +{ + return m_nBlockSize; +} + +// Define VALIDATE_SBH_FREE_LIST to a given block size to validate that pool's freelist (it'll crash on the next alloc/free after the list is corrupted) +// NOTE: this may affect perf more than USE_LIGHT_MEM_DEBUG +//#define VALIDATE_SBH_FREE_LIST 320 +template <typename CAllocator> +void CSmallBlockPool<CAllocator>::ValidateFreelist( SharedData_t *pSharedData ) +{ +#ifdef VALIDATE_SBH_FREE_LIST + if ( m_nBlockSize != VALIDATE_SBH_FREE_LIST ) + return; + static int count = 0; + count++; // Track when the corruption occurs, if repeatable + pSharedData->m_Lock.LockForWrite(); +#ifdef USE_NATIVE_SLIST + TSLNodeBase_t *pNode = (TSLNodeBase_t *)(m_FreeList.AccessUnprotected()->Next.Next); +#else + TSLNodeBase_t *pNode = (TSLNodeBase_t *)(m_FreeList.AccessUnprotected()->value.Next); +#endif + while( pNode ) + pNode = pNode->Next; + pSharedData->m_Lock.UnlockWrite(); +#endif // VALIDATE_SBH_FREE_LIST +} + +template <typename CAllocator> +void *CSmallBlockPool<CAllocator>::Alloc() +{ + SharedData_t *pSharedData = GetSharedData(); + + ValidateFreelist( pSharedData ); + + CThreadSpinRWLock &sharedLock = pSharedData->m_Lock; + if ( !sharedLock.TryLockForRead() ) + { + sharedLock.LockForRead(); + } + byte *pResult; + intp iPage = -1; + int iThreadPriority = INT_MAX; + + while (1) + { + pResult = m_FreeList.Pop(); + if ( !pResult ) + { + int nBlockSize = m_nBlockSize; + byte *pNextAlloc; + while (1) + { + pResult = m_pNextAlloc; + if ( pResult ) + { + pNextAlloc = pResult + nBlockSize; + if ( ( ( (uintp)(pNextAlloc) - 1 ) % BYTES_PAGE ) + nBlockSize > BYTES_PAGE ) + { + // Crossed a page boundary + pNextAlloc = 0; + } + if ( m_pNextAlloc.AssignIf( pResult, pNextAlloc ) ) + { + iPage = (size_t)((byte *)pResult - pSharedData->m_pBase) / BYTES_PAGE; + break; + } + } + else if ( m_CommitMutex.TryLock() ) + { + if ( !m_pNextAlloc ) + { + PageStatus_t *pAllocatedPageStatus = (PageStatus_t *)pSharedData->m_FreePages.Pop(); + if ( pAllocatedPageStatus ) + { + iPage = pAllocatedPageStatus - &pSharedData->m_PageStatus[0]; + } + else + { + while (1) + { + byte *pBlock = pSharedData->m_pNextBlock; + if ( pBlock >= pSharedData->m_pLimit ) + { + break; + } + if ( ThreadInterlockedAssignPointerIf( (void **)&pSharedData->m_pNextBlock, (void *)( pBlock + BYTES_PAGE ), (void *)pBlock ) ) + { + iPage = (size_t)((byte *)pBlock - pSharedData->m_pBase) / BYTES_PAGE; + pAllocatedPageStatus = &pSharedData->m_PageStatus[iPage]; + break; + } + } + } + + if ( pAllocatedPageStatus ) + { + byte *pBlock = pSharedData->m_pBase + ( iPage * BYTES_PAGE ); + if ( pAllocatedPageStatus->m_nAllocated == NOT_COMMITTED ) + { + pSharedData->m_Allocator.Commit( pBlock ); + } + + pAllocatedPageStatus->m_pPool = this; + pAllocatedPageStatus->m_nAllocated = 0; + pAllocatedPageStatus->m_pNextPageInPool = m_pFirstPage; + m_pFirstPage = pAllocatedPageStatus; +#ifdef TRACK_SBH_COUNTS + m_nFreeBlocks += ( BYTES_PAGE / m_nBlockSize ); +#endif + m_nCommittedPages++; + m_pNextAlloc = pBlock; + } + else + { + m_pNextAlloc = NULL; + m_CommitMutex.Unlock(); + sharedLock.UnlockRead(); + return NULL; + } + } + m_CommitMutex.Unlock(); + } + else + { + if ( iThreadPriority == INT_MAX) + { + iThreadPriority = ThreadGetPriority(); + } + + if ( iThreadPriority > 0 ) + { + ThreadSleep( 0 ); + } + } + } + + if ( pResult ) + { + break; + } + } + else + { + iPage = (size_t)((byte *)pResult - pSharedData->m_pBase) / BYTES_PAGE; + break; + } + } + +#ifdef TRACK_SBH_COUNTS + --m_nFreeBlocks; +#endif + ++pSharedData->m_PageStatus[iPage].m_nAllocated; + sharedLock.UnlockRead(); + + return pResult; +} + +template <typename CAllocator> +void CSmallBlockPool<CAllocator>::Free( void *p ) +{ + SharedData_t *pSharedData = GetSharedData(); + size_t iPage = (size_t)((byte *)p - pSharedData->m_pBase) / BYTES_PAGE; + + CThreadSpinRWLock &sharedLock = pSharedData->m_Lock; + if ( !sharedLock.TryLockForRead() ) + { + sharedLock.LockForRead(); + } + --pSharedData->m_PageStatus[iPage].m_nAllocated; +#ifdef TRACK_SBH_COUNTS + ++m_nFreeBlocks; +#endif + m_FreeList.Push( p ); + pSharedData->m_Lock.UnlockRead(); + + ValidateFreelist( pSharedData ); +} + +// Count the free blocks. +template <typename CAllocator> +int CSmallBlockPool<CAllocator>::CountFreeBlocks() +{ +#ifdef TRACK_SBH_COUNTS + return m_nFreeBlocks; +#else + return 0; +#endif +} + +// Size of committed memory managed by this heap: +template <typename CAllocator> +int CSmallBlockPool<CAllocator>::GetCommittedSize() +{ + return m_nCommittedPages * BYTES_PAGE; +} + +// Return the total blocks memory is committed for in the heap +template <typename CAllocator> +int CSmallBlockPool<CAllocator>::CountCommittedBlocks() +{ + return m_nCommittedPages * ( BYTES_PAGE / m_nBlockSize ); +} + +// Count the number of allocated blocks in the heap: +template <typename CAllocator> +int CSmallBlockPool<CAllocator>::CountAllocatedBlocks() +{ +#ifdef TRACK_SBH_COUNTS + return CountCommittedBlocks() - CountFreeBlocks(); +#else + return 0; +#endif +} + +template <typename CAllocator> +int CSmallBlockPool<CAllocator>::PageSort( const void *p1, const void *p2 ) +{ + SharedData_t *pSharedData = GetSharedData(); + return pSharedData->m_PageStatus[*((int *)p1)].m_SortList.Count() - pSharedData->m_PageStatus[*((int *)p2)].m_SortList.Count(); +} + + +template <typename CAllocator> +bool CSmallBlockPool<CAllocator>::RemovePagesFromFreeList( byte **pPages, int nPages, bool bSortList ) +{ + // Since we don't use the depth of the tslist, and sequence is only used for push, we can remove in-place + int i; + byte **pLimits = (byte **)stackalloc( nPages * sizeof(byte *) ); + int nBlocksNotInFreeList = 0; + for ( i = 0; i < nPages; i++ ) + { + pLimits[i] = pPages[i] + BYTES_PAGE; + + if ( m_pNextAlloc >= pPages[i] && m_pNextAlloc < pLimits[i] ) + { + nBlocksNotInFreeList = ( pLimits[i] - m_pNextAlloc ) / m_nBlockSize; + m_pNextAlloc = NULL; + } + } + + int iTarget = ( ( BYTES_PAGE/m_nBlockSize ) * nPages ) - nBlocksNotInFreeList; + int iCount = 0; + + TSLHead_t *pRawFreeList = m_FreeList.AccessUnprotected(); + bool bRemove; + if ( !bSortList || m_nCommittedPages - nPages == 1 ) + { +#ifdef USE_NATIVE_SLIST + TSLNodeBase_t **ppPrevNext = (TSLNodeBase_t **)&(pRawFreeList->Next); +#else + TSLNodeBase_t **ppPrevNext = (TSLNodeBase_t **)&(pRawFreeList->value.Next); +#endif + TSLNodeBase_t *pNode = *ppPrevNext; + while ( pNode && iCount != iTarget ) + { + bRemove = false; + for ( i = 0; i < nPages; i++ ) + { + if ( (byte *)pNode >= pPages[i] && (byte *)pNode < pLimits[i] ) + { + bRemove = true; + break; + } + } + + if ( bRemove ) + { + iCount++; + *ppPrevNext = pNode->Next; + } + else + { + *ppPrevNext = pNode; + ppPrevNext = &pNode->Next; + } + pNode = pNode->Next; + } + } + else + { + SharedData_t *pSharedData = GetSharedData(); + byte *pSharedBase = pSharedData->m_pBase; + TSLNodeBase_t *pNode = m_FreeList.Detach(); + TSLNodeBase_t *pNext; + int iSortPage; + + int nSortPages = 0; + int *sortPages = (int *)stackalloc( m_nCommittedPages * sizeof(int) ); + while ( pNode ) + { + pNext = pNode->Next; + bRemove = false; + for ( i = 0; i < nPages; i++ ) + { + if ( (byte *)pNode >= pPages[i] && (byte *)pNode < pLimits[i] ) + { + iCount++; + bRemove = true; + break; + } + } + + if ( !bRemove ) + { + iSortPage = ( (byte *)pNode - pSharedBase ) / BYTES_PAGE; + if ( !pSharedData->m_PageStatus[iSortPage].m_SortList.Count() ) + { + sortPages[nSortPages++] = iSortPage; + } + pSharedData->m_PageStatus[iSortPage].m_SortList.Push( pNode ); + } + + pNode = pNext; + } + + if ( nSortPages > 1 ) + { + qsort( sortPages, nSortPages, sizeof(int), &PageSort ); + } + for ( i = 0; i < nSortPages; i++ ) + { + while ( ( pNode = pSharedData->m_PageStatus[sortPages[i]].m_SortList.Pop() ) != NULL ) + { + m_FreeList.Push( pNode ); + } + } + } + if ( iTarget != iCount ) + { + DebuggerBreakIfDebugging(); + } + + return ( iTarget == iCount ); +} + + +template <typename CAllocator> +size_t CSmallBlockPool<CAllocator>::Compact( bool bIncremental ) +{ + static bool bWarnedCorruption; + bool bIsCorrupt = false; + int i; + size_t nFreed = 0; + SharedData_t *pSharedData = GetSharedData(); + pSharedData->m_Lock.LockForWrite(); + + if ( m_pFirstPage ) + { + PageStatus_t **pReleasedPages = (PageStatus_t **)stackalloc( m_nCommittedPages * sizeof(PageStatus_t *) ); + PageStatus_t **pReleasedPagesPrevs = (PageStatus_t **)stackalloc( m_nCommittedPages * sizeof(PageStatus_t *) ); + byte **pPageBases = (byte **)stackalloc( m_nCommittedPages * sizeof(byte *) ); + int nPages = 0; + + // Gather the pages to return to the backing pool + PageStatus_t *pPage = m_pFirstPage; + PageStatus_t *pPagePrev = NULL; + while ( pPage ) + { + if ( pPage->m_nAllocated == 0 ) + { + pReleasedPages[nPages] = pPage; + pPageBases[nPages] = pSharedData->m_pBase + ( pPage - &pSharedData->m_PageStatus[0] ) * BYTES_PAGE; + pReleasedPagesPrevs[nPages] = pPagePrev; + nPages++; + + if ( bIncremental ) + { + break; + } + } + pPagePrev = pPage; + pPage = pPage->m_pNextPageInPool; + } + + if ( nPages ) + { + // Remove the pages from the pool's free list + if ( !RemovePagesFromFreeList( pPageBases, nPages, !bIncremental ) && !bWarnedCorruption ) + { + // We don't know which of the pages encountered an incomplete free list + // so we'll just push them all back in and hope for the best. This isn't + // ventilator control software! + bWarnedCorruption = true; + bIsCorrupt = true; + } + + nFreed = nPages * BYTES_PAGE; + m_nCommittedPages -= nPages; + +#ifdef TRACK_SBH_COUNTS + m_nFreeBlocks -= nPages * ( BYTES_PAGE / m_nBlockSize ); +#endif + + // Unlink the pages + for ( i = nPages - 1; i >= 0; --i ) + { + if ( pReleasedPagesPrevs[i] ) + { + pReleasedPagesPrevs[i]->m_pNextPageInPool = pReleasedPages[i]->m_pNextPageInPool; + } + else + { + m_pFirstPage = pReleasedPages[i]->m_pNextPageInPool; + } + pReleasedPages[i]->m_pNextPageInPool = NULL; + pReleasedPages[i]->m_pPool = NULL; + } + + // Push them onto the backing free lists + if ( !pSharedData->m_Allocator.IsVirtual() ) + { + for ( i = 0; i < nPages; i++ ) + { + pSharedData->m_FreePages.Push( pReleasedPages[i] ); + } + } + else + { + int nMinReserve = ( bIncremental ) ? CAllocator::MIN_RESERVE_PAGES * 8 : CAllocator::MIN_RESERVE_PAGES; + int nReserveNeeded = nMinReserve - pSharedData->m_FreePages.Count(); + if ( nReserveNeeded > 0 ) + { + int nToKeepCommitted = MIN( nReserveNeeded, nPages ); + while ( nToKeepCommitted-- ) + { + nPages--; + pSharedData->m_FreePages.Push( pReleasedPages[nPages] ); + } + } + + if ( nPages ) + { + // Detach the list, push the decommitted page on, iterate up to previous + // decommits, but them on, then push the committed pages on + TSLNodeBase_t *pNodes = pSharedData->m_FreePages.Detach(); + for ( i = 0; i < nPages; i++ ) + { + pReleasedPages[i]->m_nAllocated = NOT_COMMITTED; + pSharedData->m_Allocator.Decommit( pPageBases[i] ); + pSharedData->m_FreePages.Push( pReleasedPages[i] ); + } + + TSLNodeBase_t *pCur, *pTemp = NULL; + pCur = pNodes; + while ( pCur ) + { + if ( ((PageStatus_t *)pCur)->m_nAllocated == NOT_COMMITTED ) + { + if ( pTemp ) + { + pTemp->Next = NULL; + } + else + { + pNodes = NULL; // The list only has decommitted pages, don't go circular + } + + while ( pCur ) + { + pTemp = pCur->Next; + pSharedData->m_FreePages.Push( pCur ); + pCur = pTemp; + } + break; + } + pTemp = pCur; + pCur = pCur->Next; + } + + while ( pNodes ) + { + pTemp = pNodes->Next; + pSharedData->m_FreePages.Push( pNodes ); + pNodes = pTemp; + } + } + } + } + } + pSharedData->m_Lock.UnlockWrite(); + if ( bIsCorrupt ) + { + Warning( "***** HEAP IS CORRUPT (free compromised for block size %d,in %s heap, possible write after free *****)\n", m_nBlockSize, ( pSharedData->m_Allocator.IsVirtual() ) ? "virtual" : "physical" ); + } + return nFreed; +} + +template <typename CAllocator> +bool CSmallBlockPool<CAllocator>::Validate() +{ +#ifdef NO_SBH + return true; +#else + int invalid = 0; + + SharedData_t *pSharedData = GetSharedData(); + pSharedData->m_Lock.LockForWrite(); + + byte **pPageBases = (byte **)stackalloc( m_nCommittedPages * sizeof(byte *) ); + unsigned *pageCounts = (unsigned *)stackalloc( m_nCommittedPages * sizeof(unsigned) ); + memset( pageCounts, 0, m_nCommittedPages * sizeof(int) ); + unsigned nPages = 0; + unsigned sumAllocated = 0; + unsigned freeNotInFreeList = 0; + + // Validate page list is consistent + if ( !m_pFirstPage ) + { + if ( m_nCommittedPages != 0 ) + { + invalid = __LINE__; + goto notValid; + } + } + else + { + PageStatus_t *pPage = m_pFirstPage; + while ( pPage ) + { + pPageBases[nPages] = pSharedData->m_pBase + ( pPage - &pSharedData->m_PageStatus[0] ) * BYTES_PAGE; + if ( pPage->m_pPool != this ) + { + invalid = __LINE__; + goto notValid; + } + if ( nPages > m_nCommittedPages ) + { + invalid = __LINE__; + goto notValid; + } + sumAllocated += pPage->m_nAllocated; + if ( m_pNextAlloc >= pPageBases[nPages] && m_pNextAlloc < pPageBases[nPages] + BYTES_PAGE ) + { + freeNotInFreeList = pageCounts[nPages] = ( ( pPageBases[nPages] + BYTES_PAGE ) - m_pNextAlloc ) / m_nBlockSize; + } + + nPages++; + pPage = pPage->m_pNextPageInPool; + }; + + if ( nPages != m_nCommittedPages ) + { + invalid = __LINE__; + goto notValid; + } + } + + // Validate block counts + { + unsigned blocksPerPage = ( BYTES_PAGE / m_nBlockSize ); +#ifdef USE_NATIVE_SLIST + TSLNodeBase_t *pNode = (TSLNodeBase_t *)(m_FreeList.AccessUnprotected()->Next.Next); +#else + TSLNodeBase_t *pNode = (TSLNodeBase_t *)(m_FreeList.AccessUnprotected()->value.Next); +#endif + unsigned i; + while ( pNode ) + { + for ( i = 0; i < nPages; i++ ) + { + if ( (byte *)pNode >= pPageBases[i] && (byte *)pNode < pPageBases[i] + BYTES_PAGE ) + { + pageCounts[i]++; + break; + } + } + + if ( i == nPages ) + { + invalid = __LINE__; + goto notValid; + } + + pNode = pNode->Next; + } + + PageStatus_t *pPage = m_pFirstPage; + i = 0; + while ( pPage ) + { + unsigned nFreeOnPage = blocksPerPage - pPage->m_nAllocated; + if ( nFreeOnPage != pageCounts[i++] ) + { + invalid = __LINE__; + goto notValid; + } + pPage = pPage->m_pNextPageInPool; + } + } + +notValid: + pSharedData->m_Lock.UnlockWrite(); + + if ( invalid != 0 ) + { + return false; + } + + return true; +#endif +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template <typename CAllocator> +CSmallBlockHeap<CAllocator>::CSmallBlockHeap() +{ + m_pSharedData = CPool::GetSharedData(); + + // Build a lookup table used to find the correct pool based on size + const int MAX_TABLE = MAX_SBH_BLOCK >> 2; + int i = 0; + int nBytesElement = 0; + CPool *pCurPool = NULL; + int iCurPool = 0; + + // Blocks sized 0 - 128 are in pools in increments of 8 + for ( ; i < 32; i++ ) + { + if ( (i + 1) % 2 == 1) + { + nBytesElement += 8; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 129 - 256 are in pools in increments of 16 + for ( ; i < 64; i++ ) + { + if ( (i + 1) % 4 == 1) + { + nBytesElement += 16; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } + + + // Blocks sized 257 - 512 are in pools in increments of 32 + for ( ; i < 128; i++ ) + { + if ( (i + 1) % 8 == 1) + { + nBytesElement += 32; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 513 - 768 are in pools in increments of 64 + for ( ; i < 192; i++ ) + { + if ( (i + 1) % 16 == 1) + { + nBytesElement += 64; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 769 - 1024 are in pools in increments of 128 + for ( ; i < 256; i++ ) + { + if ( (i + 1) % 32 == 1) + { + nBytesElement += 128; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 1025 - 2048 are in pools in increments of 256 + for ( ; i < MAX_TABLE; i++ ) + { + if ( (i + 1) % 64 == 1) + { + nBytesElement += 256; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } + + Assert( iCurPool == NUM_POOLS ); +} + +template <typename CAllocator> +bool CSmallBlockHeap<CAllocator>::ShouldUse( size_t nBytes ) +{ + return ( nBytes <= MAX_SBH_BLOCK ); +} + +template <typename CAllocator> +bool CSmallBlockHeap<CAllocator>::IsOwner( void * p ) +{ + if ( uintp(p) >= uintp(m_pSharedData->m_pBase) ) + { + intp index = (intp)((byte *)p - m_pSharedData->m_pBase) / BYTES_PAGE; + return ( index < ARRAYSIZE(m_pSharedData->m_PageStatus) ); + } + return false; +} + +template <typename CAllocator> +void *CSmallBlockHeap<CAllocator>::Alloc( size_t nBytes ) +{ + if ( nBytes == 0) + { + nBytes = 1; + } + Assert( ShouldUse( nBytes ) ); + CPool *pPool = FindPool( nBytes ); + void *p = pPool->Alloc(); + return p; +} + +template <typename CAllocator> +void *CSmallBlockHeap<CAllocator>::Realloc( void *p, size_t nBytes ) +{ + if ( nBytes == 0) + { + nBytes = 1; + } + + CPool *pOldPool = FindPool( p ); + CPool *pNewPool = ( ShouldUse( nBytes ) ) ? FindPool( nBytes ) : NULL; + + if ( pOldPool == pNewPool ) + { + return p; + } + + void *pNewBlock = NULL; + + if ( !pNewBlock ) + { + pNewBlock = MemAlloc_Alloc( nBytes ); // Call back out so blocks can move from the secondary to the primary pools + } + + if ( !pNewBlock ) + { + pNewBlock = malloc_internal( DEF_REGION, nBytes ); + } + + if ( pNewBlock ) + { + size_t nBytesCopy = MIN( nBytes, pOldPool->GetBlockSize() ); + memcpy( pNewBlock, p, nBytesCopy ); + } + else if ( nBytes < pOldPool->GetBlockSize() ) + { + return p; + } + + pOldPool->Free( p ); + + return pNewBlock; +} + +template <typename CAllocator> +void CSmallBlockHeap<CAllocator>::Free( void *p ) +{ + CPool *pPool = FindPool( p ); + if ( pPool ) + { + pPool->Free( p ); + } + else + { + // we probably didn't hook some allocation and now we're freeing it or the heap has been trashed! + DebuggerBreakIfDebugging(); + } +} + +template <typename CAllocator> +size_t CSmallBlockHeap<CAllocator>::GetSize( void *p ) +{ + CPool *pPool = FindPool( p ); + return pPool->GetBlockSize(); +} + +template <typename CAllocator> +void CSmallBlockHeap<CAllocator>::Usage( size_t &bytesCommitted, size_t &bytesAllocated ) +{ + bytesCommitted = 0; + bytesAllocated = 0; + for ( int i = 0; i < NUM_POOLS; i++ ) + { + bytesCommitted += m_Pools[i].GetCommittedSize(); + bytesAllocated += ( m_Pools[i].CountAllocatedBlocks() * m_Pools[i].GetBlockSize() ); + } +} + +template <typename CAllocator> +void CSmallBlockHeap<CAllocator>::DumpStats( const char *pszTag, FILE *pFile ) +{ + size_t bytesCommitted, bytesAllocated; + Usage( bytesCommitted, bytesAllocated ); + + if ( pFile ) + { + + for ( int i = 0; i < NUM_POOLS; i++ ) + { + // output for vxconsole parsing + fprintf( pFile, "Pool %2i: (size: %4u) blocks: allocated:%5i free:%5i committed:%5i (committed size:%4u kb)\n", + i, + m_Pools[i].GetBlockSize(), + m_Pools[i].CountAllocatedBlocks(), + m_Pools[i].CountFreeBlocks(), + m_Pools[i].CountCommittedBlocks(), + m_Pools[i].GetCommittedSize() ); + } + fprintf( pFile, "Totals (%s): Committed:%5u kb Allocated:%5u kb\n", pszTag, bytesCommitted / 1024, bytesAllocated / 1024 ); + } + else + { + for ( int i = 0; i < NUM_POOLS; i++ ) + { + Msg( "Pool %2i: (size: %4u) blocks: allocated:%5i free:%5i committed:%5i (committed size:%4u kb)\n",i, m_Pools[i].GetBlockSize(),m_Pools[i].CountAllocatedBlocks(), m_Pools[i].CountFreeBlocks(),m_Pools[i].CountCommittedBlocks(), m_Pools[i].GetCommittedSize() / 1024); + } + + Msg( "Totals (%s): Committed:%5u kb Allocated:%5u kb\n", pszTag, bytesCommitted / 1024, bytesAllocated / 1024 ); + } +} + +template <typename CAllocator> +CSmallBlockPool<CAllocator> *CSmallBlockHeap<CAllocator>::FindPool( size_t nBytes ) +{ + return m_PoolLookup[(nBytes - 1) >> 2]; +} + +template <typename CAllocator> +CSmallBlockPool<CAllocator> *CSmallBlockHeap<CAllocator>::FindPool( void *p ) +{ + // NOTE: If p < m_pBase, cast to unsigned size_t will cause it to be large + size_t index = (size_t)((byte *)p - m_pSharedData->m_pBase) / BYTES_PAGE; + if ( index < ARRAYSIZE(m_pSharedData->m_PageStatus) ) + return m_pSharedData->m_PageStatus[index].m_pPool; + return NULL; +} + +template <typename CAllocator> +size_t CSmallBlockHeap<CAllocator>::Compact( bool bIncremental ) +{ + size_t nRecovered = 0; + if ( bIncremental ) + { + static int iLastIncremental; + + iLastIncremental++; + for ( int i = 0; i < NUM_POOLS; i++ ) + { + int idx = ( i + iLastIncremental ) % NUM_POOLS; + nRecovered = m_Pools[idx].Compact( bIncremental ); + if ( nRecovered ) + { + iLastIncremental = idx; + break; + } + + } + } + else + { + for ( int i = 0; i < NUM_POOLS; i++ ) + { + nRecovered += m_Pools[i].Compact( bIncremental ); + } + } + return nRecovered; +} + +template <typename CAllocator> +bool CSmallBlockHeap<CAllocator>::Validate() +{ + bool valid = true; + for ( int i = 0; i < NUM_POOLS; i++ ) + { + valid = m_Pools[i].Validate() && valid; + } + return valid; +} + +#endif // MEM_SBH_ENABLED + + +//----------------------------------------------------------------------------- +// Lightweight memory tracking +//----------------------------------------------------------------------------- + +#ifdef USE_LIGHT_MEM_DEBUG + +#ifndef LIGHT_MEM_DEBUG_REQUIRES_CMD_LINE_SWITCH +#define UsingLMD() true +#else // LIGHT_MEM_DEBUG_REQUIRES_CMD_LINE_SWITCH +bool g_bUsingLMD = ( Plat_GetCommandLineA() ) ? ( strstr( Plat_GetCommandLineA(), "-uselmd" ) != NULL ) : false; +#define UsingLMD() g_bUsingLMD +#if defined( _PS3 ) +#error "Plat_GetCommandLineA() not implemented on PS3" +#endif +#endif // LIGHT_MEM_DEBUG_REQUIRES_CMD_LINE_SWITCH + +const char *g_pszUnknown = "unknown"; + +struct Sentinal_t +{ + DWORD value[4]; +}; + +Sentinal_t g_HeadSentinel = +{ + 0xdeadbeef, + 0xbaadf00d, + 0xbd122969, + 0xdeadbeef, +}; + +Sentinal_t g_TailSentinel = +{ + 0xbaadf00d, + 0xbd122969, + 0xdeadbeef, + 0xbaadf00d, +}; + +const byte g_FreeFill = 0xdd; + +static const uint LWD_FREE = 0; +static const uint LWD_ALLOCATED = 1; + +#define LMD_STATUS_BITS ( 1 ) +#define LMD_ALIGN_BITS ( 32 - LMD_STATUS_BITS ) +#define LMD_MAX_ALIGN ( 1 << ( LMD_ALIGN_BITS - 1) ) + +struct AllocHeader_t +{ + const char *pszModule; + int line; + size_t nBytes; + uint status : LMD_STATUS_BITS; + uint align : LMD_ALIGN_BITS; + Sentinal_t sentinal; +}; + +const int g_nRecentFrees = ( IsPC() ) ? 8192 : 512; +AllocHeader_t **g_pRecentFrees = (AllocHeader_t **)calloc( g_nRecentFrees, sizeof(AllocHeader_t *) ); +int g_iNextFreeSlot; + +#define INTERNAL_INLINE + +#define LMDToHeader( pUserPtr ) ( ((AllocHeader_t *)(pUserPtr)) - 1 ) +#define LMDFromHeader( pHeader ) ( (byte *)((pHeader) + 1) ) + +CThreadFastMutex g_LMDMutex; + +const char *g_pLMDFileName = NULL; +int g_nLMDLine; +int g_iLMDDepth; + +void LMDPushAllocDbgInfo( const char *pFileName, int nLine ) +{ + if ( ThreadInMainThread() ) + { + if ( !g_iLMDDepth ) + { + g_pLMDFileName = pFileName; + g_nLMDLine = nLine; + } + g_iLMDDepth++; + } +} + +void LMDPopAllocDbgInfo() +{ + if ( ThreadInMainThread() && g_iLMDDepth > 0 ) + { + g_iLMDDepth--; + if ( g_iLMDDepth == 0 ) + { + g_pLMDFileName = NULL; + g_nLMDLine = 0; + } + } +} + + +void LMDReportInvalidBlock( AllocHeader_t *pHeader, const char *pszMessage ) +{ + char szMsg[256]; + if ( pHeader ) + { + sprintf( szMsg, "HEAP IS CORRUPT: %s (block 0x%x, size %d, alignment %d)\n", pszMessage, (size_t)LMDFromHeader( pHeader ), pHeader->nBytes, pHeader->align ); + } + else + { + sprintf( szMsg, "HEAP IS CORRUPT: %s\n", pszMessage ); + } + if ( Plat_IsInDebugSession() ) + { + DebuggerBreak(); + } + else + { + WriteMiniDump(); + } +#ifdef IS_WINDOWS_PC + ::MessageBox( NULL, szMsg, "Error", MB_SYSTEMMODAL | MB_OK ); +#else + Warning( szMsg ); +#endif +} + +void LMDValidateBlock( AllocHeader_t *pHeader, bool bFreeList ) +{ + if ( !pHeader ) + return; + + if ( memcmp( &pHeader->sentinal, &g_HeadSentinel, sizeof(Sentinal_t) ) != 0 ) + { + LMDReportInvalidBlock( pHeader, "Head sentinel corrupt" ); + } + if ( memcmp( ((Sentinal_t *)(LMDFromHeader( pHeader ) + pHeader->nBytes)), &g_TailSentinel, sizeof(Sentinal_t) ) != 0 ) + { + LMDReportInvalidBlock( pHeader, "Tail sentinel corrupt" ); + } + if ( bFreeList ) + { + byte *pCur = (byte *)pHeader + sizeof(AllocHeader_t); + byte *pLimit = pCur + pHeader->nBytes; + while ( pCur != pLimit ) + { + if ( *pCur++ != g_FreeFill ) + { + LMDReportInvalidBlock( pHeader, "Write after free" ); + } + } + } +} + + +size_t LMDComputeHeaderSize( size_t align = 0 ) +{ + if ( !align ) + return sizeof(AllocHeader_t); + // For aligned allocs, the header is preceded by padding which maintains alignment + if ( align > LMD_MAX_ALIGN ) + s_StdMemAlloc.SetCRTAllocFailed( align ); // TODO: could convert alignment to exponent to get around this, or use a flag for alignments over 1KB or 1MB... + return ( ( sizeof( AllocHeader_t ) + (align-1) ) & ~(align-1) ); +} + +size_t LMDAdjustSize( size_t &nBytes, size_t align = 0 ) +{ + if ( !UsingLMD() ) + return nBytes; + // Add data before+after each alloc + return ( nBytes + LMDComputeHeaderSize( align ) + sizeof(Sentinal_t) ); +} + +void *LMDNoteAlloc( void *p, size_t nBytes, size_t align = 0, const char *pszModule = g_pszUnknown, int line = 0 ) +{ + if ( !UsingLMD() ) + { + return p; + } + + if ( g_pLMDFileName ) + { + pszModule = g_pLMDFileName; + line = g_nLMDLine; + } + + if ( p ) + { + byte *pUserPtr = ((byte*)p) + LMDComputeHeaderSize( align ); + AllocHeader_t *pHeader = LMDToHeader( pUserPtr ); + pHeader->pszModule = pszModule; + pHeader->line = line; + pHeader->status = LWD_ALLOCATED; + pHeader->nBytes = nBytes; + pHeader->align = (uint)align; + pHeader->sentinal = g_HeadSentinel; + *((Sentinal_t *)(pUserPtr + pHeader->nBytes)) = g_TailSentinel; + LMDValidateBlock( pHeader, false ); + return pUserPtr; + } + return NULL; + + // Some SBH clients rely on allocations > 16 bytes being 16-byte aligned, so we mustn't break that assumption: + MEMSTD_COMPILE_TIME_ASSERT( sizeof( AllocHeader_t ) % 16 == 0 ); +} + +void *LMDNoteFree( void *p ) +{ + if ( !UsingLMD() ) + { + return p; + } + + AUTO_LOCK( g_LMDMutex ); + if ( !p ) + { + return NULL; + } + + AllocHeader_t *pHeader = LMDToHeader( p ); + if ( pHeader->status == LWD_FREE ) + { + LMDReportInvalidBlock( pHeader, "Double free" ); + } + LMDValidateBlock( pHeader, false ); + + AllocHeader_t *pToReturn; + if ( pHeader->nBytes < 16*1024 ) + { + pToReturn = g_pRecentFrees[g_iNextFreeSlot]; + LMDValidateBlock( pToReturn, true ); + + g_pRecentFrees[g_iNextFreeSlot] = pHeader; + g_iNextFreeSlot = (g_iNextFreeSlot + 1 ) % g_nRecentFrees; + } + else + { + pToReturn = pHeader; + LMDValidateBlock( g_pRecentFrees[rand() % g_nRecentFrees], true ); + } + + pHeader->status = LWD_FREE; + memset( pHeader + 1, g_FreeFill, pHeader->nBytes ); + + if ( pToReturn && ( pToReturn->align ) ) + { + // For aligned allocations, the actual system allocation starts *before* the LMD header: + size_t headerPadding = LMDComputeHeaderSize( pToReturn->align ) - sizeof( AllocHeader_t ); + return ( ((byte*)pToReturn) - headerPadding ); + } + + return pToReturn; +} + +size_t LMDGetSize( void *p ) +{ + if ( !UsingLMD() ) + { + return (size_t)(-1); + } + + AllocHeader_t *pHeader = LMDToHeader( p ); + return pHeader->nBytes; +} + +bool LMDValidateHeap() +{ + if ( !UsingLMD() ) + { + return true; + } + + AUTO_LOCK( g_LMDMutex ); + for ( int i = 0; i < g_nRecentFrees && g_pRecentFrees[i]; i++ ) + { + LMDValidateBlock( g_pRecentFrees[i], true ); + } + return true; +} + +void *LMDRealloc( void *pMem, size_t nSize, size_t align = 0, const char *pszModule = g_pszUnknown, int line = 0 ) +{ + if ( nSize == 0 ) + { + s_StdMemAlloc.Free( pMem ); + return NULL; + } + void *pNew; +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + if ( align ) + pNew = s_StdMemAlloc.AllocAlign( nSize, align, pszModule, line ); + else +#endif // MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + pNew = s_StdMemAlloc.Alloc( nSize, pszModule, line ); + if ( !pMem ) + { + return pNew; + } + AllocHeader_t *pHeader = LMDToHeader( pMem ); + if ( align != pHeader->align ) + { + LMDReportInvalidBlock( pHeader, "Realloc changed alignment!" ); + } + size_t nCopySize = MIN( nSize, pHeader->nBytes ); + memcpy( pNew, pMem, nCopySize ); + s_StdMemAlloc.Free( pMem, pszModule, line ); + return pNew; +} + +#else // USE_LIGHT_MEM_DEBUG + +#define INTERNAL_INLINE FORCEINLINE +#define UsingLMD() false +FORCEINLINE size_t LMDAdjustSize( size_t &nBytes, size_t align = 0 ) { return nBytes; } +#define LMDNoteAlloc( pHeader, ... ) (pHeader) +#define LMDNoteFree( pHeader, ... ) (pHeader) +#define LMDGetSize( pHeader ) (size_t)(-1) +#define LMDToHeader( pHeader ) (pHeader) +#define LMDFromHeader( pHeader ) (pHeader) +#define LMDValidateHeap() (true) +#define LMDPushAllocDbgInfo( pFileName, nLine ) ((void)0) +#define LMDPopAllocDbgInfo() ((void)0) +FORCEINLINE void *LMDRealloc( void *pMem, size_t nSize, size_t align = 0, const char *pszModule = NULL, int line = 0 ) { return NULL; } + +#endif // USE_LIGHT_MEM_DEBUG + +//----------------------------------------------------------------------------- +// Internal versions +//----------------------------------------------------------------------------- + +INTERNAL_INLINE void *CStdMemAlloc::InternalAllocFromPools( size_t nSize ) +{ +#if MEM_SBH_ENABLED + void *pMem; + + pMem = m_PrimarySBH.Alloc( nSize ); + if ( pMem ) + { + return pMem; + } + +#ifdef MEMALLOC_USE_SECONDARY_SBH + pMem = m_SecondarySBH.Alloc( nSize ); + if ( pMem ) + { + return pMem; + } +#endif // MEMALLOC_USE_SECONDARY_SBH + +#ifndef MEMALLOC_NO_FALLBACK + pMem = m_FallbackSBH.Alloc( nSize ); + if ( pMem ) + { + return pMem; + } +#endif // MEMALLOC_NO_FALLBACK + + CallAllocFailHandler( nSize ); +#endif // MEM_SBH_ENABLED + return NULL; +} + +INTERNAL_INLINE void *CStdMemAlloc::InternalAlloc( int region, size_t nSize ) +{ + PROFILE_ALLOC(Malloc); + + void *pMem; + +#if MEM_SBH_ENABLED + if ( m_PrimarySBH.ShouldUse( nSize ) ) // test valid for either pool + { + pMem = InternalAllocFromPools( nSize ); + if ( !pMem ) + { + CompactOnFail(); + pMem = InternalAllocFromPools( nSize ); + } + if ( pMem ) + { + ApplyMemoryInitializations( pMem, nSize ); + return pMem; + } + + ExecuteOnce( DevWarning( "\n\nDRASTIC MEMORY OVERFLOW: Fell out of small block heap!\n\n\n") ); + } +#endif // MEM_SBH_ENABLED + + pMem = malloc_internal( region, nSize ); + if ( !pMem ) + { + CompactOnFail(); + pMem = malloc_internal( region, nSize ); + if ( !pMem ) + { + SetCRTAllocFailed( nSize ); + return NULL; + } + } + + ApplyMemoryInitializations( pMem, nSize ); + return pMem; +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +INTERNAL_INLINE void *CStdMemAlloc::InternalAllocAligned( int region, size_t nSize, size_t align ) +{ + PROFILE_ALLOC(MallocAligned); + + void *pMem; + +#if MEM_SBH_ENABLED + size_t nSizeAligned = ( nSize + align - 1 ) & ~( align - 1 ); + if ( m_PrimarySBH.ShouldUse( nSizeAligned ) ) // test valid for either pool + { + pMem = InternalAllocFromPools( nSizeAligned ); + if ( !pMem ) + { + CompactOnFail(); + pMem = InternalAllocFromPools( nSizeAligned ); + } + if ( pMem ) + { + ApplyMemoryInitializations( pMem, nSizeAligned ); + return pMem; + } + + ExecuteOnce( DevWarning( "Warning: Fell out of small block heap!\n") ); + } +#endif // MEM_SBH_ENABLED + + pMem = malloc_aligned_internal( region, nSize, align ); + if ( !pMem ) + { + CompactOnFail(); + pMem = malloc_aligned_internal( region, nSize, align ); + if ( !pMem ) + { + SetCRTAllocFailed( nSize ); + return NULL; + } + } + + ApplyMemoryInitializations( pMem, nSize ); + return pMem; +} +#endif // MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + +INTERNAL_INLINE void *CStdMemAlloc::InternalRealloc( void *pMem, size_t nSize ) +{ + if ( !pMem ) + { + return RegionAlloc( DEF_REGION, nSize ); + } + + PROFILE_ALLOC(Realloc); + +#if MEM_SBH_ENABLED + if ( m_PrimarySBH.IsOwner( pMem ) ) + { + return m_PrimarySBH.Realloc( pMem, nSize ); + } + +#ifdef MEMALLOC_USE_SECONDARY_SBH + if ( m_SecondarySBH.IsOwner( pMem ) ) + { + return m_SecondarySBH.Realloc( pMem, nSize ); + } + +#endif // MEMALLOC_USE_SECONDARY_SBH + +#ifndef MEMALLOC_NO_FALLBACK + if ( m_FallbackSBH.IsOwner( pMem ) ) + { + return m_FallbackSBH.Realloc( pMem, nSize ); + } +#endif // MEMALLOC_NO_FALLBACK + +#endif // MEM_SBH_ENABLED + + void *pRet = realloc_internal( pMem, nSize ); + if ( !pRet ) + { + CompactOnFail(); + pRet = realloc_internal( pMem, nSize ); + if ( !pRet ) + { + SetCRTAllocFailed( nSize ); + } + } + + return pRet; +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +INTERNAL_INLINE void *CStdMemAlloc::InternalReallocAligned( void *pMem, size_t nSize, size_t align ) +{ + if ( !pMem ) + { + return InternalAllocAligned( DEF_REGION, nSize, align ); + } + + PROFILE_ALLOC(ReallocAligned); + +#if MEM_SBH_ENABLED + if ( m_PrimarySBH.IsOwner( pMem ) ) + { + return m_PrimarySBH.Realloc( pMem, nSize ); + } + +#ifdef MEMALLOC_USE_SECONDARY_SBH + if ( m_SecondarySBH.IsOwner( pMem ) ) + { + return m_SecondarySBH.Realloc( pMem, nSize ); + } +#endif // MEMALLOC_USE_SECONDARY_SBH + +#ifndef MEMALLOC_NO_FALLBACK + if ( m_FallbackSBH.IsOwner( pMem ) ) + { + return m_FallbackSBH.Realloc( pMem, nSize ); + } +#endif // MEMALLOC_NO_FALLBACK + +#endif // MEM_SBH_ENABLED + + void *pRet = realloc_aligned_internal( pMem, nSize, align ); + if ( !pRet ) + { + CompactOnFail(); + pRet = realloc_aligned_internal( pMem, nSize, align ); + if ( !pRet ) + { + SetCRTAllocFailed( nSize ); + } + } + + return pRet; +} +#endif + +INTERNAL_INLINE void CStdMemAlloc::InternalFree( void *pMem ) +{ + if ( !pMem ) + { + return; + } + + PROFILE_ALLOC(Free); + +#if MEM_SBH_ENABLED + if ( m_PrimarySBH.IsOwner( pMem ) ) + { + m_PrimarySBH.Free( pMem ); + return; + } + +#ifdef MEMALLOC_USE_SECONDARY_SBH + if ( m_SecondarySBH.IsOwner( pMem ) ) + { + return m_SecondarySBH.Free( pMem ); + } +#endif // MEMALLOC_USE_SECONDARY_SBH + +#ifndef MEMALLOC_NO_FALLBACK + if ( m_FallbackSBH.IsOwner( pMem ) ) + { + m_FallbackSBH.Free( pMem ); + return; + } +#endif // MEMALLOC_NO_FALLBACK + +#endif // MEM_SBH_ENABLED + + free_internal( pMem ); +} + +void CStdMemAlloc::CompactOnFail() +{ + CompactHeap(); +} + +//----------------------------------------------------------------------------- +// Release versions +//----------------------------------------------------------------------------- + +void *CStdMemAlloc::Alloc( size_t nSize ) +{ + size_t nAdjustedSize = LMDAdjustSize( nSize ); + return LMDNoteAlloc( CStdMemAlloc::InternalAlloc( DEF_REGION, nAdjustedSize ), nSize ); +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +void * CStdMemAlloc::AllocAlign( size_t nSize, size_t align ) +{ + size_t nAdjustedSize = LMDAdjustSize( nSize, align ); + return LMDNoteAlloc( CStdMemAlloc::InternalAllocAligned( DEF_REGION, nAdjustedSize, align ), nSize, align ); +} +#endif // MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + +void *CStdMemAlloc::Realloc( void *pMem, size_t nSize ) +{ + if ( UsingLMD() ) + return LMDRealloc( pMem, nSize ); + return CStdMemAlloc::InternalRealloc( pMem, nSize ); +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +void * CStdMemAlloc::ReallocAlign( void *pMem, size_t nSize, size_t align ) +{ + if ( UsingLMD() ) + return LMDRealloc( pMem, nSize, align ); + return CStdMemAlloc::InternalReallocAligned( pMem, nSize, align ); +} +#endif // MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + +void CStdMemAlloc::Free( void *pMem ) +{ + pMem = LMDNoteFree( pMem ); + CStdMemAlloc::InternalFree( pMem ); +} + +void *CStdMemAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize ) +{ + return NULL; +} + +//----------------------------------------------------------------------------- +// Debug versions +//----------------------------------------------------------------------------- +void *CStdMemAlloc::Alloc( size_t nSize, const char *pFileName, int nLine ) +{ + size_t nAdjustedSize = LMDAdjustSize( nSize ); + return LMDNoteAlloc( CStdMemAlloc::InternalAlloc( DEF_REGION, nAdjustedSize ), nSize, 0, pFileName, nLine ); +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +void *CStdMemAlloc::AllocAlign( size_t nSize, size_t align, const char *pFileName, int nLine ) +{ + size_t nAdjustedSize = LMDAdjustSize( nSize, align ); + return LMDNoteAlloc( CStdMemAlloc::InternalAllocAligned( DEF_REGION, nAdjustedSize, align ), nSize, align, pFileName, nLine ); +} +#endif // MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + +void *CStdMemAlloc::Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + if ( UsingLMD() ) + return LMDRealloc( pMem, nSize, 0, pFileName, nLine ); + return CStdMemAlloc::InternalRealloc( pMem, nSize ); +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +void * CStdMemAlloc::ReallocAlign( void *pMem, size_t nSize, size_t align, const char *pFileName, int nLine ) +{ + if ( UsingLMD() ) + return LMDRealloc( pMem, nSize, align, pFileName, nLine ); + return CStdMemAlloc::InternalReallocAligned( pMem, nSize, align ); +} +#endif // MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + +void CStdMemAlloc::Free( void *pMem, const char *pFileName, int nLine ) +{ + pMem = LMDNoteFree( pMem ); + CStdMemAlloc::InternalFree( pMem ); +} + +void *CStdMemAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + return NULL; +} + +//----------------------------------------------------------------------------- +// Region support +//----------------------------------------------------------------------------- +void *CStdMemAlloc::RegionAlloc( int region, size_t nSize ) +{ + size_t nAdjustedSize = LMDAdjustSize( nSize ); + return LMDNoteAlloc( CStdMemAlloc::InternalAlloc( region, nAdjustedSize ), nSize ); +} + +void *CStdMemAlloc::RegionAlloc( int region, size_t nSize, const char *pFileName, int nLine ) +{ + size_t nAdjustedSize = LMDAdjustSize( nSize ); + return LMDNoteAlloc( CStdMemAlloc::InternalAlloc( region, nAdjustedSize ), nSize, 0, pFileName, nLine ); +} + +#if defined (LINUX) +#include <malloc.h> +#elif defined (OSX) +#define malloc_usable_size( ptr ) malloc_size( ptr ) +extern "C" { + extern size_t malloc_size( const void *ptr ); +} +#endif // LINUX/OSX + +//----------------------------------------------------------------------------- +// Returns the size of a particular allocation (NOTE: may be larger than the size requested!) +//----------------------------------------------------------------------------- +size_t CStdMemAlloc::GetSize( void *pMem ) +{ + if ( !pMem ) + return CalcHeapUsed(); + + if ( UsingLMD() ) + { + return LMDGetSize( pMem ); + } + +#if MEM_SBH_ENABLED + if ( m_PrimarySBH.IsOwner( pMem ) ) + { + return m_PrimarySBH.GetSize( pMem ); + } + +#ifdef MEMALLOC_USE_SECONDARY_SBH + if ( m_SecondarySBH.IsOwner( pMem ) ) + { + return m_SecondarySBH.GetSize( pMem ); + } +#endif // MEMALLOC_USE_SECONDARY_SBH + +#ifndef MEMALLOC_NO_FALLBACK + if ( m_FallbackSBH.IsOwner( pMem ) ) + { + return m_FallbackSBH.GetSize( pMem ); + } +#endif // MEMALLOC_NO_FALLBACK + +#endif // MEM_SBH_ENABLED + + return msize_internal( pMem ); +} + + +//----------------------------------------------------------------------------- +// Force file + line information for an allocation +//----------------------------------------------------------------------------- +void CStdMemAlloc::PushAllocDbgInfo( const char *pFileName, int nLine ) +{ + LMDPushAllocDbgInfo( pFileName, nLine ); +} + +void CStdMemAlloc::PopAllocDbgInfo() +{ + LMDPopAllocDbgInfo(); +} + +//----------------------------------------------------------------------------- +// FIXME: Remove when we make our own heap! Crt stuff we're currently using +//----------------------------------------------------------------------------- +int32 CStdMemAlloc::CrtSetBreakAlloc( int32 lNewBreakAlloc ) +{ + return 0; +} + +int CStdMemAlloc::CrtSetReportMode( int nReportType, int nReportMode ) +{ + return 0; +} + +int CStdMemAlloc::CrtIsValidHeapPointer( const void *pMem ) +{ + return 1; +} + +int CStdMemAlloc::CrtIsValidPointer( const void *pMem, unsigned int size, int access ) +{ + return 1; +} + +int CStdMemAlloc::CrtCheckMemory( void ) +{ +#ifndef _CERT + LMDValidateHeap(); +#if MEM_SBH_ENABLED + if ( !m_PrimarySBH.Validate() ) + { + ExecuteOnce( Msg( "Small block heap is corrupt (primary)\n " ) ); + } +#ifdef MEMALLOC_USE_SECONDARY_SBH + if ( !m_SecondarySBH.Validate() ) + { + ExecuteOnce( Msg( "Small block heap is corrupt (secondary)\n " ) ); + } +#endif // MEMALLOC_USE_SECONDARY_SBH +#ifndef MEMALLOC_NO_FALLBACK + if ( !m_FallbackSBH.Validate() ) + { + ExecuteOnce( Msg( "Small block heap is corrupt (fallback)\n " ) ); + } +#endif // MEMALLOC_NO_FALLBACK +#endif // MEM_SBH_ENABLED +#endif // _CERT + return 1; +} + +int CStdMemAlloc::CrtSetDbgFlag( int nNewFlag ) +{ + return 0; +} + +void CStdMemAlloc::CrtMemCheckpoint( _CrtMemState *pState ) +{ +} + +// FIXME: Remove when we have our own allocator +void* CStdMemAlloc::CrtSetReportFile( int nRptType, void* hFile ) +{ + return 0; +} + +void* CStdMemAlloc::CrtSetReportHook( void* pfnNewHook ) +{ + return 0; +} + +int CStdMemAlloc::CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * pMsg ) +{ + return 0; +} + +int CStdMemAlloc::heapchk() +{ +#ifdef _WIN32 + CrtCheckMemory(); + return _HEAPOK; +#else + return 1; +#endif +} + +void CStdMemAlloc::DumpStats() +{ + DumpStatsFileBase( "memstats" ); +} + +void CStdMemAlloc::DumpStatsFileBase( char const *pchFileBase ) +{ +#if defined( _WIN32 ) || defined( _GAMECONSOLE ) + char filename[ 512 ]; + _snprintf( filename, sizeof( filename ) - 1, +#ifdef _X360 + "D:\\%s.txt", +#elif defined( _PS3 ) + "/app_home/%s.txt", +#else + "%s.txt", +#endif + pchFileBase ); + filename[ sizeof( filename ) - 1 ] = 0; + FILE *pFile = ( IsGameConsole() ) ? NULL : fopen( filename, "wt" ); + +#if MEM_SBH_ENABLED + if ( pFile ) + fprintf( pFile, "Fixed Page SBH:\n" ); + else + Msg( "Fixed Page SBH:\n" ); + m_PrimarySBH.DumpStats("Fixed Page SBH", pFile); +#ifdef MEMALLOC_USE_SECONDARY_SBH + if ( pFile ) + fprintf( pFile, "Secondary Fixed Page SBH:\n" ); + else + Msg( "Secondary Page SBH:\n" ); + m_SecondarySBH.DumpStats("Secondary Page SBH", pFile); +#endif // MEMALLOC_USE_SECONDARY_SBH +#ifndef MEMALLOC_NO_FALLBACK + if ( pFile ) + fprintf( pFile, "\nFallback SBH:\n" ); + else + Msg( "\nFallback SBH:\n" ); + m_FallbackSBH.DumpStats("Fallback SBH", pFile); // Dump statistics to small block heap +#endif // MEMALLOC_NO_FALLBACK +#endif // MEM_SBH_ENABLED + +#ifdef _PS3 + malloc_managed_size mms; + (g_pMemOverrideRawCrtFns->pfn_malloc_stats)( &mms ); + Msg( "PS3 malloc_stats: %u / %u / %u \n", mms.current_inuse_size, mms.current_system_size, mms.max_system_size ); +#endif // _PS3 + + heapstats_internal( pFile ); +#if defined( _X360 ) + XBX_rMemDump( filename ); +#endif + + if ( pFile ) + fclose( pFile ); +#endif // _WIN32 || _GAMECONSOLE +} + +IVirtualMemorySection * CStdMemAlloc::AllocateVirtualMemorySection( size_t numMaxBytes ) +{ +#if defined( _GAMECONSOLE ) || defined( _WIN32 ) + extern IVirtualMemorySection * VirtualMemoryManager_AllocateVirtualMemorySection( size_t numMaxBytes ); + return VirtualMemoryManager_AllocateVirtualMemorySection( numMaxBytes ); +#else + return NULL; +#endif +} + +size_t CStdMemAlloc::ComputeMemoryUsedBy( char const *pchSubStr ) +{ + return 0;//dbg heap only. +} + +static inline size_t ExtraDevkitMemory( void ) +{ +#if defined( _PS3 ) + // 213MB are available in retail mode, so adjust free mem to reflect that even if we're in devkit mode + const size_t RETAIL_SIZE = 213*1024*1024; + static sys_memory_info stat; + sys_memory_get_user_memory_size( &stat ); + if ( stat.total_user_memory > RETAIL_SIZE ) + return ( stat.total_user_memory - RETAIL_SIZE ); +#elif defined( _X360 ) + // TODO: detect the new 1GB devkit... +#endif // _PS3/_X360 + return 0; +} + +void CStdMemAlloc::GlobalMemoryStatus( size_t *pUsedMemory, size_t *pFreeMemory ) +{ + if ( !pUsedMemory || !pFreeMemory ) + return; + + size_t dlMallocFree = 0; +#if defined( USE_DLMALLOC ) + // Account for free memory contained within DLMalloc's FIRST region. The rationale is as follows: + // - the first region is supposed to service large allocations via virtual allocation, and to grow as + // needed (until all physical pages are used), so true 'out of memory' failures should occur there. + // - other regions (the 2-256kb 'medium block heap', or per-DLL heaps, and the Small Block Heap) + // are sized to a pre-determined high watermark, and not intended to grow. Free memory within + // those regions is not available for large allocations, so adding that to the 'free memory' + // yields confusing data which does not correspond well with out-of-memory failures. + mallinfo info = mspace_mallinfo( g_AllocRegions[ 0 ] ); + dlMallocFree += info.fordblks; +#endif // USE_DLMALLOC + +#if defined ( _X360 ) + + // GlobalMemoryStatus tells us how much physical memory is free + MEMORYSTATUS stat; + ::GlobalMemoryStatus( &stat ); + *pFreeMemory = stat.dwAvailPhys; + *pFreeMemory += dlMallocFree; + // Adjust free mem to reflect a retail box, even if we're using a devkit with extra memory + *pFreeMemory -= ExtraDevkitMemory(); + + // Used is total minus free (discount the 32MB system reservation) + *pUsedMemory = ( stat.dwTotalPhys - 32*1024*1024 ) - *pFreeMemory; + +#elif defined( _PS3 ) + + // NOTE: we use dlmalloc instead of the system heap, so we do NOT count the system heap's free space! + //static malloc_managed_size mms; + //(g_pMemOverrideRawCrtFns->pfn_malloc_stats)( &mms ); + //int heapFree = mms.current_system_size - mms.current_inuse_size; + + // sys_memory_get_user_memory_size tells us how much PPU memory is used/free + static sys_memory_info stat; + sys_memory_get_user_memory_size( &stat ); + *pFreeMemory = stat.available_user_memory; + *pFreeMemory += dlMallocFree; + *pUsedMemory = stat.total_user_memory - *pFreeMemory; + // Adjust free mem to reflect a retail box, even if we're using a devkit with extra memory + *pFreeMemory -= ExtraDevkitMemory(); + +#else // _X360/_PS3/other + + // no data + *pFreeMemory = 0; + *pUsedMemory = 0; + +#endif // _X360/_PS3//other +} + +#define MAX_GENERIC_MEMORY_STATS 64 +GenericMemoryStat_t g_MemStats[MAX_GENERIC_MEMORY_STATS]; +int g_nMemStats = 0; +static inline int AddGenericMemoryStat( const char *name, int value ) +{ + Assert( g_nMemStats < MAX_GENERIC_MEMORY_STATS ); + if ( g_nMemStats < MAX_GENERIC_MEMORY_STATS ) + { + g_MemStats[ g_nMemStats ].name = name; + g_MemStats[ g_nMemStats ].value = value; + g_nMemStats++; + } + return g_nMemStats; +} + +int CStdMemAlloc::GetGenericMemoryStats( GenericMemoryStat_t **ppMemoryStats ) +{ + if ( !ppMemoryStats ) + return 0; + g_nMemStats = 0; + +#if MEM_SBH_ENABLED + { + // Small block heap + size_t SBHCommitted = 0, SBHAllocated = 0; + size_t commitTmp, allocTmp; +#if MEM_SBH_ENABLED + m_PrimarySBH.Usage( commitTmp, allocTmp ); + SBHCommitted += commitTmp; SBHAllocated += allocTmp; +#ifdef MEMALLOC_USE_SECONDARY_SBH + m_SecondarySBH.Usage( commitTmp, allocTmp ); + SBHCommitted += commitTmp; SBHAllocated += allocTmp; +#endif // MEMALLOC_USE_SECONDARY_SBH +#ifndef MEMALLOC_NO_FALLBACK + m_FallbackSBH.Usage( commitTmp, allocTmp ); + SBHCommitted += commitTmp; SBHAllocated += allocTmp; +#endif // MEMALLOC_NO_FALLBACK +#endif // MEM_SBH_ENABLED + + static size_t SBHMaxCommitted = 0; SBHMaxCommitted = MAX( SBHMaxCommitted, SBHCommitted ); + AddGenericMemoryStat( "SBH_cur", (int)SBHCommitted ); + AddGenericMemoryStat( "SBH_max", (int)SBHMaxCommitted ); + } +#endif // MEM_SBH_ENABLED + +#if defined( USE_DLMALLOC ) +#if !defined( MEMALLOC_REGIONS ) && defined( MEMALLOC_SEGMENT_MIXED ) + { + // Medium block heap + mallinfo infoMBH = mspace_mallinfo( g_AllocRegions[ 1 ] ); + size_t nMBHCurUsed = infoMBH.uordblks;// nMBH_WRONG_MaxUsed = infoMBH.usmblks; // TODO: figure out why dlmalloc mis-reports MBH max usage (it just returns the footprint) + static size_t nMBHMaxUsed = 0; nMBHMaxUsed = MAX( nMBHMaxUsed, nMBHCurUsed ); + AddGenericMemoryStat( "MBH_cur", (int)nMBHCurUsed ); + AddGenericMemoryStat( "MBH_max", (int)nMBHMaxUsed ); + + // Large block heap + mallinfo infoLBH = mspace_mallinfo( g_AllocRegions[ 0 ] ); + size_t nLBHCurUsed = mspace_footprint( g_AllocRegions[ 0 ] ), nLBHMaxUsed = mspace_max_footprint( g_AllocRegions[ 0 ] ), nLBHArenaSize = infoLBH.arena, nLBHFree = infoLBH.fordblks; + AddGenericMemoryStat( "LBH_cur", (int)nLBHCurUsed ); + AddGenericMemoryStat( "LBH_max", (int)nLBHMaxUsed ); + // LBH arena used+free (these are non-virtual allocations - there should be none, since we only allocate 256KB+ items in the LBH) + // TODO: I currently see the arena grow to 320KB due to a larger allocation being realloced down... if this gets worse, add an 'ALWAYS use VMM' flag to the mspace. + AddGenericMemoryStat( "LBH_arena", (int)nLBHArenaSize ); + AddGenericMemoryStat( "LBH_free", (int)nLBHFree ); + } +#else // (!MEMALLOC_REGIONS && MEMALLOC_SEGMENT_MIXED) + { + // Single dlmalloc heap (TODO: per-DLL heap stats, if we resurrect that) + mallinfo info = mspace_mallinfo( g_AllocRegions[ 0 ] ); + AddGenericMemoryStat( "mspace_cur", (int)info.uordblks ); + AddGenericMemoryStat( "mspace_max", (int)info.usmblks ); + AddGenericMemoryStat( "mspace_size", (int)mspace_footprint( g_AllocRegions[ 0 ] ) ); + } +#endif // (!MEMALLOC_REGIONS && MEMALLOC_SEGMENT_MIXED) +#endif // USE_DLMALLOC + + size_t nMaxPhysMemUsed_Delta; + nMaxPhysMemUsed_Delta = 0; +#ifdef _PS3 + { + // System heap (should not exist!) + static malloc_managed_size mms; + (g_pMemOverrideRawCrtFns->pfn_malloc_stats)( &mms ); + if ( mms.current_system_size ) + AddGenericMemoryStat( "sys_heap", (int)mms.current_system_size ); + + // Virtual Memory Manager + size_t nReserved = 0, nReservedMax = 0, nCommitted = 0, nCommittedMax = 0; + extern void VirtualMemoryManager_GetStats( size_t &nReserved, size_t &nReservedMax, size_t &nCommitted, size_t &nCommittedMax ); + VirtualMemoryManager_GetStats( nReserved, nReservedMax, nCommitted, nCommittedMax ); + AddGenericMemoryStat( "VMM_reserved", (int)nReserved ); + AddGenericMemoryStat( "VMM_reserved_max", (int)nReservedMax ); + AddGenericMemoryStat( "VMM_committed", (int)nCommitted ); + AddGenericMemoryStat( "VMM_committed_max", (int)nCommittedMax ); + + // Estimate memory committed by memory stacks (these account for all VMM allocations other than the SBH/MBH/LBH) + size_t nHeapTotal = 1024*1024*MBYTES_PRIMARY_SBH; +#if defined( USE_DLMALLOC ) + for ( int i = 0; i < ARRAYSIZE(g_AllocRegions); i++ ) + { + nHeapTotal += mspace_footprint( g_AllocRegions[i] ); + } +#endif // USE_DLMALLOC + size_t nMemStackTotal = nCommitted - nHeapTotal; + AddGenericMemoryStat( "MemStacks", (int)nMemStackTotal ); + + // On PS3, we can more accurately determine 'phys_free_min', since we know nCommittedMax + // (otherwise nPhysFreeMin is only updated intermittently; when this function is called): + nMaxPhysMemUsed_Delta = nCommittedMax - nCommitted; + } +#endif // _PS3 + +#if defined( _GAMECONSOLE ) + // Total/free/min-free physical pages + { +#if defined( _X360 ) + MEMORYSTATUS stat; + ::GlobalMemoryStatus( &stat ); + size_t nPhysTotal = stat.dwTotalPhys, nPhysFree = stat.dwAvailPhys - ExtraDevkitMemory(); +#elif defined( _PS3 ) + static sys_memory_info stat; + sys_memory_get_user_memory_size( &stat ); + size_t nPhysTotal = stat.total_user_memory, nPhysFree = stat.available_user_memory - ExtraDevkitMemory(); +#endif // _X360/_PS3 + static size_t nPhysFreeMin = nPhysTotal; + nPhysFreeMin = MIN( nPhysFreeMin, ( nPhysFree - nMaxPhysMemUsed_Delta ) ); + AddGenericMemoryStat( "phys_total", (int)nPhysTotal ); + AddGenericMemoryStat( "phys_free", (int)nPhysFree ); + AddGenericMemoryStat( "phys_free_min", (int)nPhysFreeMin ); + } +#endif // _GAMECONSOLE + + *ppMemoryStats = &g_MemStats[0]; + return g_nMemStats; +} + +void CStdMemAlloc::CompactHeap() +{ +#if MEM_SBH_ENABLED + if ( !m_CompactMutex.TryLock() ) + { + return; + } + if ( m_bInCompact ) + { + m_CompactMutex.Unlock(); + return; + } + + m_bInCompact = true; + size_t nBytesRecovered; +#ifndef MEMALLOC_NO_FALLBACK + nBytesRecovered = m_FallbackSBH.Compact( false ); + if ( nBytesRecovered && IsGameConsole() ) + { + Msg( "Compact freed %d bytes from virtual heap (up to 256k still committed)\n", nBytesRecovered ); + } +#endif // MEMALLOC_NO_FALLBACK + nBytesRecovered = m_PrimarySBH.Compact( false ); +#ifdef MEMALLOC_USE_SECONDARY_SBH + nBytesRecovered += m_SecondarySBH.Compact( false ); +#endif + if ( nBytesRecovered && IsGameConsole() ) + { + Msg( "Compact released %d bytes from the SBH\n", nBytesRecovered ); + } + + nBytesRecovered = compact_internal(); + if ( nBytesRecovered && IsGameConsole() ) + { + Msg( "Compact released %d bytes from the mixed block heap\n", nBytesRecovered ); + } + + m_bInCompact = false; + m_CompactMutex.Unlock(); +#endif // MEM_SBH_ENABLED +} + +void CStdMemAlloc::CompactIncremental() +{ +#if MEM_SBH_ENABLED + if ( !m_CompactMutex.TryLock() ) + { + return; + } + if ( m_bInCompact ) + { + m_CompactMutex.Unlock(); + return; + } + + m_bInCompact = true; +#ifndef MEMALLOC_NO_FALLBACK + m_FallbackSBH.Compact( true ); +#endif + m_PrimarySBH.Compact( true ); +#ifdef MEMALLOC_USE_SECONDARY_SBH + m_SecondarySBH.Compact( true ); +#endif + m_bInCompact = false; + m_CompactMutex.Unlock(); +#endif // MEM_SBH_ENABLED +} + +MemAllocFailHandler_t CStdMemAlloc::SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ) +{ + MemAllocFailHandler_t pfnPrevious = m_pfnFailHandler; + m_pfnFailHandler = pfnMemAllocFailHandler; + return pfnPrevious; +} + +size_t CStdMemAlloc::DefaultFailHandler( size_t nBytes ) +{ + if ( IsX360() ) + { +#ifdef _X360 + ExecuteOnce( + { + char buffer[256]; + _snprintf( buffer, sizeof( buffer ), "***** Memory pool overflow, attempted allocation size: %u (not a critical error)\n", nBytes ); + XBX_OutputDebugString( buffer ); + } + ); +#endif // _X360 + } + return 0; +} + +void CStdMemAlloc::SetStatsExtraInfo( const char *pMapName, const char *pComment ) +{ +} + +void CStdMemAlloc::SetCRTAllocFailed( size_t nSize ) +{ + m_sMemoryAllocFailed = nSize; + + DebuggerBreakIfDebugging(); +#if defined( _PS3 ) && defined( _DEBUG ) + DebuggerBreak(); +#endif // _PS3 + + char buffer[256]; +#ifdef COMPILER_GCC + _snprintf( buffer, sizeof( buffer ), "***** OUT OF MEMORY! attempted allocation size: %u ****\n", nSize ); +#else + _snprintf( buffer, sizeof( buffer ), "***** OUT OF MEMORY! attempted allocation size: %u ****\n", nSize ); +#endif // COMPILER_GCC + +#ifdef _X360 + XBX_OutputDebugString( buffer ); + if ( !Plat_IsInDebugSession() ) + { + XBX_CrashDump( true ); +#if defined( _DEMO ) + XLaunchNewImage( XLAUNCH_KEYWORD_DEFAULT_APP, 0 ); +#else + XLaunchNewImage( "default.xex", 0 ); +#endif // _DEMO + } +#elif defined(_WIN32 ) + OutputDebugString( buffer ); + if ( !Plat_IsInDebugSession() ) + { + WriteMiniDump(); + abort(); + } +#else // _X360/_WIN32/other + printf( "%s\n", buffer ); + if ( !Plat_IsInDebugSession() ) + { + WriteMiniDump(); +#if defined( _PS3 ) + DumpStats(); +#endif + Plat_ExitProcess( 0 ); + } +#endif // _X360/_WIN32/other + +} + +size_t CStdMemAlloc::MemoryAllocFailed() +{ + return m_sMemoryAllocFailed; +} + +#endif // MEM_IMPL_TYPE_STD + +#endif // STEAM diff --git a/external/vpc/tier0/memstd.h b/external/vpc/tier0/memstd.h new file mode 100644 index 0000000..ea594c4 --- /dev/null +++ b/external/vpc/tier0/memstd.h @@ -0,0 +1,452 @@ +//----------------------------------------------------------------------------- +// NOTE! This should never be called directly from leaf code +// Just use new,delete,malloc,free etc. They will call into this eventually +//----------------------------------------------------------------------------- +#include "pch_tier0.h" + +#if IS_WINDOWS_PC +#define WIN_32_LEAN_AND_MEAN +#include <windows.h> +#define VA_COMMIT_FLAGS MEM_COMMIT +#define VA_RESERVE_FLAGS MEM_RESERVE +#elif defined( _X360 ) +#undef Verify +#define _XBOX +#include <xtl.h> +#undef _XBOX +#include "xbox/xbox_win32stubs.h" +#define VA_COMMIT_FLAGS (MEM_COMMIT|MEM_NOZERO|MEM_LARGE_PAGES) +#define VA_RESERVE_FLAGS (MEM_RESERVE|MEM_LARGE_PAGES) +#elif defined( _PS3 ) +#include "sys/memory.h" +#include "sys/mempool.h" +#include "sys/process.h" +#include <sys/vm.h> + +#endif + +//#include <malloc.h> +#include <algorithm> +#include "tier0/dbg.h" +#include "tier0/memalloc.h" +#include "tier0/threadtools.h" +#include "tier0/tslist.h" +#include "mem_helpers.h" + +#ifndef _PS3 +#pragma pack(4) +#endif + +#define MIN_SBH_BLOCK 8 +#define MIN_SBH_ALIGN 8 +#define MAX_SBH_BLOCK 2048 +#define MAX_POOL_REGION (4*1024*1024) + + +#define NUM_POOLS 42 + +#if defined( _WIN32 ) || defined( _PS3 ) +// FIXME: Disable small block heap on win64 for now; it's busted because +// it's expecting SLIST_HEADER to look different than it does on win64 +#if !defined( PLATFORM_WINDOWS_PC64 ) +#define MEM_SBH_ENABLED 1 +#endif +#endif + +#if !defined(_CERT) && ( defined(_X360) || defined(_PS3) ) +#define TRACK_SBH_COUNTS +#endif + +#if defined(_X360) + +// 360 uses a 48MB primary (physical) SBH and 10MB secondary (virtual) SBH, with no fallback +#define MBYTES_PRIMARY_SBH 48 +#define MEMALLOC_USE_SECONDARY_SBH +#define MBYTES_SECONDARY_SBH 10 +#define MEMALLOC_NO_FALLBACK + +#elif defined(_PS3) + +// PS3 uses just a 32MB SBH - this was enough to avoid overflow when Portal 2 shipped. +// NOTE: when Steam uses the game's tier0 allocator (see memalloc.h), we increase the size +// of the SBH and MBH (see memstd.cpp) to accommodate those extra allocations. +#define MBYTES_PRIMARY_SBH ( 32 + MBYTES_STEAM_SBH_USAGE ) +#define MEMALLOC_NO_FALLBACK + +#else // _X360 | _PS3 + +// Other platforms use a 48MB primary SBH and a (32MB) fallback SBH +#define MBYTES_PRIMARY_SBH 48 + +#endif // _X360 | _PS3 + +#define MEMSTD_COMPILE_TIME_ASSERT( pred ) switch(0){case 0:case pred:;} + +//----------------------------------------------------------------------------- +// Small block pool +//----------------------------------------------------------------------------- + +class CFreeList : public CTSListBase +{ +public: + void Push( void *p ) { CTSListBase::Push( (TSLNodeBase_t *)p ); } + byte *Pop() { return (byte *)CTSListBase::Pop(); } +}; + +template <typename CAllocator> +class CSmallBlockHeap; + +template <typename CAllocator> +class CSmallBlockPool +{ +public: + CSmallBlockPool() + { + m_nBlockSize = 0; + m_nCommittedPages = 0; + m_pFirstPage = NULL; + } + + void Init( unsigned nBlockSize ); + size_t GetBlockSize(); + void *Alloc(); + void Free( void *p ); + int CountFreeBlocks(); + int GetCommittedSize(); + int CountCommittedBlocks(); + int CountAllocatedBlocks(); + size_t Compact( bool bIncremental ); + bool Validate(); + + enum + { + BYTES_PAGE = CAllocator::BYTES_PAGE, + NOT_COMMITTED = -1 + }; + +private: + typedef CSmallBlockHeap<CAllocator> CHeap; + friend class CSmallBlockHeap<CAllocator>; + + struct PageStatus_t : public TSLNodeBase_t + { + PageStatus_t() + { + m_pPool = NULL; + m_nAllocated = NOT_COMMITTED; + m_pNextPageInPool = NULL; + } + + CSmallBlockPool<CAllocator> * m_pPool; + PageStatus_t * m_pNextPageInPool; + CInterlockedInt m_nAllocated; + CTSListBase m_SortList; + }; + + struct SharedData_t + { + CAllocator m_Allocator; + CTSListBase m_FreePages; + CThreadSpinRWLock m_Lock; + PageStatus_t m_PageStatus[CAllocator::TOTAL_BYTES/CAllocator::BYTES_PAGE]; + byte * m_pNextBlock; + byte * m_pBase; + byte * m_pLimit; + }; + + static int PageSort( const void *p1, const void *p2 ) ; + bool RemovePagesFromFreeList( byte **pPages, int nPages, bool bSortList ); + + void ValidateFreelist( SharedData_t *pSharedData ); + + CFreeList m_FreeList; + + CInterlockedPtr<byte> m_pNextAlloc; + + PageStatus_t * m_pFirstPage; + unsigned m_nBlockSize; + unsigned m_nCommittedPages; + + CThreadFastMutex m_CommitMutex; + +#ifdef TRACK_SBH_COUNTS + CInterlockedInt m_nFreeBlocks; +#endif + + static SharedData_t *GetSharedData() + { + return &gm_SharedData; + } + + static SharedData_t gm_SharedData; +}; + +//----------------------------------------------------------------------------- +// Small block heap (multi-pool) +//----------------------------------------------------------------------------- + +template <typename CAllocator> +class CSmallBlockHeap +{ +public: + CSmallBlockHeap(); + bool ShouldUse( size_t nBytes ); + bool IsOwner( void * p ); + void *Alloc( size_t nBytes ); + void *Realloc( void *p, size_t nBytes ); + void Free( void *p ); + size_t GetSize( void *p ); + void DumpStats( const char *pszTag, FILE *pFile = NULL ); + void Usage( size_t &bytesCommitted, size_t &bytesAllocated ); + size_t Compact( bool bIncremental ); + bool Validate(); + + enum + { + BYTES_PAGE = CAllocator::BYTES_PAGE + }; + +private: + typedef CSmallBlockPool<CAllocator> CPool; + typedef struct CSmallBlockPool<CAllocator>::SharedData_t SharedData_t; + + CPool *FindPool( size_t nBytes ); + CPool *FindPool( void *p ); + + // Map size to a pool address to a pool + CPool *m_PoolLookup[MAX_SBH_BLOCK >> 2]; + CPool m_Pools[NUM_POOLS]; + + SharedData_t *m_pSharedData; +}; + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +class CStdMemAlloc : public IMemAlloc +{ +public: + CStdMemAlloc(); + + // Internal versions + void *InternalAlloc( int region, size_t nSize ); +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + void *InternalAllocAligned( int region, size_t nSize, size_t align ); +#endif + void *InternalAllocFromPools( size_t nSize ); + void *InternalRealloc( void *pMem, size_t nSize ); +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + void *InternalReallocAligned( void *pMem, size_t nSize, size_t align ); +#endif + void InternalFree( void *pMem ); + + void CompactOnFail(); + + // Release versions + virtual void *Alloc( size_t nSize ); + virtual void *Realloc( void *pMem, size_t nSize ); + virtual void Free( void *pMem ); + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize ); + + // Debug versions + virtual void *Alloc( size_t nSize, const char *pFileName, int nLine ); + virtual void *Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ); + virtual void Free( void *pMem, const char *pFileName, int nLine ); + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ); + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + virtual void *AllocAlign( size_t nSize, size_t align ); + virtual void *AllocAlign( size_t nSize, size_t align, const char *pFileName, int nLine ); + virtual void *ReallocAlign( void *pMem, size_t nSize, size_t align ); + virtual void *ReallocAlign( void *pMem, size_t nSize, size_t align, const char *pFileName, int nLine ); +#endif + + virtual void *RegionAlloc( int region, size_t nSize ); + virtual void *RegionAlloc( int region, size_t nSize, const char *pFileName, int nLine ); + + // Returns size of a particular allocation + virtual size_t GetSize( void *pMem ); + + // Force file + line information for an allocation + virtual void PushAllocDbgInfo( const char *pFileName, int nLine ); + virtual void PopAllocDbgInfo(); + + virtual int32 CrtSetBreakAlloc( int32 lNewBreakAlloc ); + virtual int CrtSetReportMode( int nReportType, int nReportMode ); + virtual int CrtIsValidHeapPointer( const void *pMem ); + virtual int CrtIsValidPointer( const void *pMem, unsigned int size, int access ); + virtual int CrtCheckMemory( void ); + virtual int CrtSetDbgFlag( int nNewFlag ); + virtual void CrtMemCheckpoint( _CrtMemState *pState ); + void* CrtSetReportFile( int nRptType, void* hFile ); + void* CrtSetReportHook( void* pfnNewHook ); + int CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * pMsg ); + virtual int heapchk(); + + virtual void DumpStats(); + virtual void DumpStatsFileBase( char const *pchFileBase ); + virtual size_t ComputeMemoryUsedBy( char const *pchSubStr ); + virtual void GlobalMemoryStatus( size_t *pUsedMemory, size_t *pFreeMemory ); + + virtual bool IsDebugHeap() { return false; } + + virtual void GetActualDbgInfo( const char *&pFileName, int &nLine ) {} + virtual void RegisterAllocation( const char *pFileName, int nLine, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) {} + virtual void RegisterDeallocation( const char *pFileName, int nLine, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) {} + + virtual int GetVersion() { return MEMALLOC_VERSION; } + + virtual void OutOfMemory( size_t nBytesAttempted = 0 ) { SetCRTAllocFailed( nBytesAttempted ); } + + virtual IVirtualMemorySection * AllocateVirtualMemorySection( size_t numMaxBytes ); + + virtual int GetGenericMemoryStats( GenericMemoryStat_t **ppMemoryStats ); + + virtual void CompactHeap(); + virtual void CompactIncremental(); + + virtual MemAllocFailHandler_t SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ); + size_t CallAllocFailHandler( size_t nBytes ) { return (*m_pfnFailHandler)( nBytes); } + + virtual uint32 GetDebugInfoSize() { return 0; } + virtual void SaveDebugInfo( void *pvDebugInfo ) { } + virtual void RestoreDebugInfo( const void *pvDebugInfo ) {} + virtual void InitDebugInfo( void *pvDebugInfo, const char *pchRootFileName, int nLine ) {} + + static size_t DefaultFailHandler( size_t ); + void DumpBlockStats( void *p ) {} + +#if MEM_SBH_ENABLED + class CVirtualAllocator + { + public: + enum + { + BYTES_PAGE = (64*1024), + TOTAL_BYTES = (32*1024*1024), + MIN_RESERVE_PAGES = 4, + }; + + byte *AllocatePoolMemory() + { +#ifdef _WIN32 + return (byte *)VirtualAlloc( NULL, TOTAL_BYTES, VA_RESERVE_FLAGS, PAGE_NOACCESS ); +#elif defined( _PS3 ) + Error( "" ); + return NULL; +#else +#error +#endif + } + + bool IsVirtual() + { + return true; + } + + bool Decommit( void *pPage ) + { +#ifdef _WIN32 + return ( VirtualFree( pPage, BYTES_PAGE, MEM_DECOMMIT ) != 0 ); +#elif defined( _PS3 ) + return false; +#else +#error +#endif + } + + bool Commit( void *pPage ) + { +#ifdef _WIN32 + return ( VirtualAlloc( pPage, BYTES_PAGE, VA_COMMIT_FLAGS, PAGE_READWRITE ) != NULL ); +#elif defined( _PS3 ) + return false; +#else +#error +#endif + } + }; + + typedef CSmallBlockHeap<CVirtualAllocator> CVirtualSmallBlockHeap; + + template <size_t SIZE_MB, bool bPhysical> + class CFixedAllocator + { + public: + enum + { + BYTES_PAGE = (16*1024), + TOTAL_BYTES = (SIZE_MB*1024*1024), + MIN_RESERVE_PAGES = TOTAL_BYTES/BYTES_PAGE, + }; + + byte *AllocatePoolMemory() + { +#ifdef _WIN32 +#ifdef _X360 + if ( bPhysical ) + return (byte *)XPhysicalAlloc( TOTAL_BYTES, MAXULONG_PTR, 4096, PAGE_READWRITE | MEM_16MB_PAGES ); +#endif + return (byte *)VirtualAlloc( NULL, TOTAL_BYTES, VA_COMMIT_FLAGS, PAGE_READWRITE ); +#elif defined( _PS3 ) + // TODO: release this section on shutdown (use GetMemorySectionForAddress) + extern IVirtualMemorySection * VirtualMemoryManager_AllocateVirtualMemorySection( size_t numMaxBytes ); + IVirtualMemorySection *pSection = VirtualMemoryManager_AllocateVirtualMemorySection( TOTAL_BYTES ); + if ( !pSection ) + Error( "CFixedAllocator::AllocatePoolMemory() failed in VirtualMemoryManager_AllocateVirtualMemorySection\n" ); + if ( !pSection->CommitPages( pSection->GetBaseAddress(), TOTAL_BYTES ) ) + Error( "CFixedAllocator::AllocatePoolMemory() failed in IVirtualMemorySection::CommitPages\n" ); + return reinterpret_cast<byte *>( pSection->GetBaseAddress() ); +#else +#error +#endif + } + + bool IsVirtual() + { + return false; + } + + bool Decommit( void *pPage ) + { + return false; + } + + bool Commit( void *pPage ) + { + return false; + } + }; + + typedef CSmallBlockHeap<CFixedAllocator< MBYTES_PRIMARY_SBH, true> > CFixedSmallBlockHeap; +#ifdef MEMALLOC_USE_SECONDARY_SBH + typedef CSmallBlockHeap<CFixedAllocator< MBYTES_SECONDARY_SBH, false> > CFixedVirtualSmallBlockHeap; // @TODO: move back into above heap if number stays at 16 [7/15/2009 tom] +#endif + + CFixedSmallBlockHeap m_PrimarySBH; +#ifdef MEMALLOC_USE_SECONDARY_SBH + CFixedVirtualSmallBlockHeap m_SecondarySBH; +#endif +#ifndef MEMALLOC_NO_FALLBACK + CVirtualSmallBlockHeap m_FallbackSBH; +#endif + +#endif // MEM_SBH_ENABLED + + + virtual void SetStatsExtraInfo( const char *pMapName, const char *pComment ); + + virtual size_t MemoryAllocFailed(); + + void SetCRTAllocFailed( size_t nMemSize ); + + MemAllocFailHandler_t m_pfnFailHandler; + size_t m_sMemoryAllocFailed; + CThreadFastMutex m_CompactMutex; + bool m_bInCompact; +}; + +#ifndef _PS3 +#pragma pack() +#endif diff --git a/external/vpc/tier0/memvalidate.cpp b/external/vpc/tier0/memvalidate.cpp new file mode 100644 index 0000000..17c9514 --- /dev/null +++ b/external/vpc/tier0/memvalidate.cpp @@ -0,0 +1,498 @@ +//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Memory allocation! +// +// $NoKeywords: $ +//=============================================================================// + +#include "pch_tier0.h" + +#ifndef STEAM + +#ifdef TIER0_VALIDATE_HEAP + +#include <malloc.h> +#include "tier0/dbg.h" +#include "tier0/memalloc.h" +#include "mem_helpers.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +extern IMemAlloc *g_pActualAlloc; + +//----------------------------------------------------------------------------- +// NOTE! This should never be called directly from leaf code +// Just use new,delete,malloc,free etc. They will call into this eventually +//----------------------------------------------------------------------------- +class CValidateAlloc : public IMemAlloc +{ +public: + enum + { + HEAP_PREFIX_BUFFER_SIZE = 12, + HEAP_SUFFIX_BUFFER_SIZE = 8, + }; + + CValidateAlloc(); + + // Release versions + virtual void *Alloc( size_t nSize ); + virtual void *Realloc( void *pMem, size_t nSize ); + virtual void Free( void *pMem ); + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize ); + + // Debug versions + virtual void *Alloc( size_t nSize, const char *pFileName, int nLine ); + virtual void *Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ); + virtual void Free( void *pMem, const char *pFileName, int nLine ); + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ); + + // Returns size of a particular allocation + virtual size_t GetSize( void *pMem ); + + // Force file + line information for an allocation + virtual void PushAllocDbgInfo( const char *pFileName, int nLine ); + virtual void PopAllocDbgInfo(); + + virtual long CrtSetBreakAlloc( long lNewBreakAlloc ); + virtual int CrtSetReportMode( int nReportType, int nReportMode ); + virtual int CrtIsValidHeapPointer( const void *pMem ); + virtual int CrtIsValidPointer( const void *pMem, unsigned int size, int access ); + virtual int CrtCheckMemory( void ); + virtual int CrtSetDbgFlag( int nNewFlag ); + virtual void CrtMemCheckpoint( _CrtMemState *pState ); + void* CrtSetReportFile( int nRptType, void* hFile ); + void* CrtSetReportHook( void* pfnNewHook ); + int CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * pMsg ); + virtual int heapchk(); + + virtual void DumpStats() {} + virtual void DumpStatsFileBase( char const *pchFileBase ) {} + + virtual bool IsDebugHeap() + { + return true; + } + + virtual int GetVersion() { return MEMALLOC_VERSION; } + + virtual void CompactHeap(); + virtual MemAllocFailHandler_t SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ); + + virtual uint32 GetDebugInfoSize() { return 0; } + virtual void SaveDebugInfo( void *pvDebugInfo ) { } + virtual void RestoreDebugInfo( const void *pvDebugInfo ) {} + virtual void InitDebugInfo( void *pvDebugInfo, const char *pchRootFileName, int nLine ) {} + +private: + struct HeapPrefix_t + { + HeapPrefix_t *m_pPrev; + HeapPrefix_t *m_pNext; + int m_nSize; + unsigned char m_Prefix[HEAP_PREFIX_BUFFER_SIZE]; + }; + + struct HeapSuffix_t + { + unsigned char m_Suffix[HEAP_SUFFIX_BUFFER_SIZE]; + }; + +private: + // Returns the actual debug info + void GetActualDbgInfo( const char *&pFileName, int &nLine ); + + // Updates stats + void RegisterAllocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ); + void RegisterDeallocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ); + + HeapSuffix_t *Suffix( HeapPrefix_t *pPrefix ); + void *AllocationStart( HeapPrefix_t *pBase ); + HeapPrefix_t *PrefixFromAllocation( void *pAlloc ); + const HeapPrefix_t *PrefixFromAllocation( const void *pAlloc ); + + // Add to the list! + void AddToList( HeapPrefix_t *pHeap, int nSize ); + + // Remove from the list! + void RemoveFromList( HeapPrefix_t *pHeap ); + + // Validate the allocation + bool ValidateAllocation( HeapPrefix_t *pHeap ); + +private: + HeapPrefix_t *m_pFirstAllocation; + char m_pPrefixImage[HEAP_PREFIX_BUFFER_SIZE]; + char m_pSuffixImage[HEAP_SUFFIX_BUFFER_SIZE]; +}; + + +//----------------------------------------------------------------------------- +// Singleton... +//----------------------------------------------------------------------------- +static CValidateAlloc s_ValidateAlloc; + +#ifdef _PS3 + +IMemAlloc *g_pMemAllocInternalPS3 = &s_ValidateAlloc; + +#else // !_PS3 + +IMemAlloc *g_pMemAlloc = &s_ValidateAlloc; + +#endif // _PS3 + + +//----------------------------------------------------------------------------- +// Constructor. +//----------------------------------------------------------------------------- +CValidateAlloc::CValidateAlloc() +{ + m_pFirstAllocation = 0; + memset( m_pPrefixImage, 0xBE, HEAP_PREFIX_BUFFER_SIZE ); + memset( m_pSuffixImage, 0xAF, HEAP_SUFFIX_BUFFER_SIZE ); +} + + +//----------------------------------------------------------------------------- +// Accessors... +//----------------------------------------------------------------------------- +inline CValidateAlloc::HeapSuffix_t *CValidateAlloc::Suffix( HeapPrefix_t *pPrefix ) +{ + return reinterpret_cast<HeapSuffix_t *>( (unsigned char*)( pPrefix + 1 ) + pPrefix->m_nSize ); +} + +inline void *CValidateAlloc::AllocationStart( HeapPrefix_t *pBase ) +{ + return static_cast<void *>( pBase + 1 ); +} + +inline CValidateAlloc::HeapPrefix_t *CValidateAlloc::PrefixFromAllocation( void *pAlloc ) +{ + if ( !pAlloc ) + return NULL; + + return ((HeapPrefix_t*)pAlloc) - 1; +} + +inline const CValidateAlloc::HeapPrefix_t *CValidateAlloc::PrefixFromAllocation( const void *pAlloc ) +{ + return ((const HeapPrefix_t*)pAlloc) - 1; +} + + +//----------------------------------------------------------------------------- +// Add to the list! +//----------------------------------------------------------------------------- +void CValidateAlloc::AddToList( HeapPrefix_t *pHeap, int nSize ) +{ + pHeap->m_pPrev = NULL; + pHeap->m_pNext = m_pFirstAllocation; + if ( m_pFirstAllocation ) + { + m_pFirstAllocation->m_pPrev = pHeap; + } + pHeap->m_nSize = nSize; + + m_pFirstAllocation = pHeap; + + HeapSuffix_t *pSuffix = Suffix( pHeap ); + memcpy( pHeap->m_Prefix, m_pPrefixImage, HEAP_PREFIX_BUFFER_SIZE ); + memcpy( pSuffix->m_Suffix, m_pSuffixImage, HEAP_SUFFIX_BUFFER_SIZE ); +} + + +//----------------------------------------------------------------------------- +// Remove from the list! +//----------------------------------------------------------------------------- +void CValidateAlloc::RemoveFromList( HeapPrefix_t *pHeap ) +{ + if ( !pHeap ) + return; + + ValidateAllocation( pHeap ); + if ( pHeap->m_pPrev ) + { + pHeap->m_pPrev->m_pNext = pHeap->m_pNext; + } + else + { + m_pFirstAllocation = pHeap->m_pNext; + } + + if ( pHeap->m_pNext ) + { + pHeap->m_pNext->m_pPrev = pHeap->m_pPrev; + } +} + + +//----------------------------------------------------------------------------- +// Validate the allocation +//----------------------------------------------------------------------------- +bool CValidateAlloc::ValidateAllocation( HeapPrefix_t *pHeap ) +{ + HeapSuffix_t *pSuffix = Suffix( pHeap ); + + bool bOk = true; + if ( memcmp( pHeap->m_Prefix, m_pPrefixImage, HEAP_PREFIX_BUFFER_SIZE ) ) + { + bOk = false; + } + + if ( memcmp( pSuffix->m_Suffix, m_pSuffixImage, HEAP_SUFFIX_BUFFER_SIZE ) ) + { + bOk = false; + } + + if ( !bOk ) + { + Warning("Memory trash detected in allocation %X!\n", (void*)(pHeap+1) ); + Assert( 0 ); + } + + return bOk; +} + +//----------------------------------------------------------------------------- +// Release versions +//----------------------------------------------------------------------------- +void *CValidateAlloc::Alloc( size_t nSize ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + HeapPrefix_t *pHeap = (HeapPrefix_t*)g_pActualAlloc->Alloc( nActualSize ); + AddToList( pHeap, nSize ); + return AllocationStart( pHeap ); +} + +void *CValidateAlloc::Realloc( void *pMem, size_t nSize ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + pHeap = (HeapPrefix_t*)g_pActualAlloc->Realloc( pHeap, nActualSize ); + AddToList( pHeap, nSize ); + + return AllocationStart( pHeap ); +} + +void CValidateAlloc::Free( void *pMem ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + g_pActualAlloc->Free( pHeap ); +} + +void *CValidateAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + pHeap = (HeapPrefix_t*)g_pActualAlloc->Expand_NoLongerSupported( pHeap, nActualSize ); + AddToList( pHeap, nSize ); + + return AllocationStart( pHeap ); +} + + +//----------------------------------------------------------------------------- +// Debug versions +//----------------------------------------------------------------------------- +void *CValidateAlloc::Alloc( size_t nSize, const char *pFileName, int nLine ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + HeapPrefix_t *pHeap = (HeapPrefix_t*)g_pActualAlloc->Alloc( nActualSize, pFileName, nLine ); + AddToList( pHeap, nSize ); + return AllocationStart( pHeap ); +} + +void *CValidateAlloc::Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + pHeap = (HeapPrefix_t*)g_pActualAlloc->Realloc( pHeap, nActualSize, pFileName, nLine ); + AddToList( pHeap, nSize ); + + return AllocationStart( pHeap ); +} + +void CValidateAlloc::Free( void *pMem, const char *pFileName, int nLine ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + g_pActualAlloc->Free( pHeap, pFileName, nLine ); +} + +void *CValidateAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + pHeap = (HeapPrefix_t*)g_pActualAlloc->Expand_NoLongerSupported( pHeap, nActualSize, pFileName, nLine ); + AddToList( pHeap, nSize ); + + return AllocationStart( pHeap ); +} + + +//----------------------------------------------------------------------------- +// Returns size of a particular allocation +//----------------------------------------------------------------------------- +size_t CValidateAlloc::GetSize( void *pMem ) +{ + if ( !pMem ) + return CalcHeapUsed(); + + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + return pHeap->m_nSize; +} + + +//----------------------------------------------------------------------------- +// Force file + line information for an allocation +//----------------------------------------------------------------------------- +void CValidateAlloc::PushAllocDbgInfo( const char *pFileName, int nLine ) +{ + g_pActualAlloc->PushAllocDbgInfo( pFileName, nLine ); +} + +void CValidateAlloc::PopAllocDbgInfo() +{ + g_pActualAlloc->PopAllocDbgInfo( ); +} + +//----------------------------------------------------------------------------- +// FIXME: Remove when we make our own heap! Crt stuff we're currently using +//----------------------------------------------------------------------------- +long CValidateAlloc::CrtSetBreakAlloc( long lNewBreakAlloc ) +{ + return g_pActualAlloc->CrtSetBreakAlloc( lNewBreakAlloc ); +} + +int CValidateAlloc::CrtSetReportMode( int nReportType, int nReportMode ) +{ + return g_pActualAlloc->CrtSetReportMode( nReportType, nReportMode ); +} + +int CValidateAlloc::CrtIsValidHeapPointer( const void *pMem ) +{ + const HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + return g_pActualAlloc->CrtIsValidHeapPointer( pHeap ); +} + +int CValidateAlloc::CrtIsValidPointer( const void *pMem, unsigned int size, int access ) +{ + const HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + return g_pActualAlloc->CrtIsValidPointer( pHeap, size, access ); +} + +int CValidateAlloc::CrtCheckMemory( void ) +{ + return g_pActualAlloc->CrtCheckMemory( ); +} + +int CValidateAlloc::CrtSetDbgFlag( int nNewFlag ) +{ + return g_pActualAlloc->CrtSetDbgFlag( nNewFlag ); +} + +void CValidateAlloc::CrtMemCheckpoint( _CrtMemState *pState ) +{ + g_pActualAlloc->CrtMemCheckpoint( pState ); +} + +void* CValidateAlloc::CrtSetReportFile( int nRptType, void* hFile ) +{ + return g_pActualAlloc->CrtSetReportFile( nRptType, hFile ); +} + +void* CValidateAlloc::CrtSetReportHook( void* pfnNewHook ) +{ + return g_pActualAlloc->CrtSetReportHook( pfnNewHook ); +} + +int CValidateAlloc::CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * pMsg ) +{ + return g_pActualAlloc->CrtDbgReport( nRptType, szFile, nLine, szModule, pMsg ); +} + +int CValidateAlloc::heapchk() +{ + bool bOk = true; + + // Validate the heap + HeapPrefix_t *pHeap = m_pFirstAllocation; + for( pHeap = m_pFirstAllocation; pHeap; pHeap = pHeap->m_pNext ) + { + if ( !ValidateAllocation( pHeap ) ) + { + bOk = false; + } + } + +#ifdef _WIN32 + return bOk ? _HEAPOK : 0; +#elif POSIX + return bOk; +#else +#error +#endif +} + +// Returns the actual debug info +void CValidateAlloc::GetActualDbgInfo( const char *&pFileName, int &nLine ) +{ + g_pActualAlloc->GetActualDbgInfo( pFileName, nLine ); +} + +// Updates stats +void CValidateAlloc::RegisterAllocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ) +{ + g_pActualAlloc->RegisterAllocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ); +} + +void CValidateAlloc::RegisterDeallocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ) +{ + g_pActualAlloc->RegisterDeallocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ); +} + +void CValidateAlloc::CompactHeap() +{ + g_pActualAlloc->CompactHeap(); +} + +MemAllocFailHandler_t CValidateAlloc::SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ) +{ + return g_pActualAlloc->SetAllocFailHandler( pfnMemAllocFailHandler ); +} + +#endif // TIER0_VALIDATE_HEAP + +#endif // STEAM diff --git a/external/vpc/tier0/minidump.cpp b/external/vpc/tier0/minidump.cpp new file mode 100644 index 0000000..7d2a85b --- /dev/null +++ b/external/vpc/tier0/minidump.cpp @@ -0,0 +1,317 @@ +//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "pch_tier0.h" +#include "tier0/minidump.h" +#include "tier0/platform.h" + + +#if defined( _WIN32 ) && !defined(_X360 ) && ( _MSC_VER >= 1300 ) + +#include "tier0/valve_off.h" +#define WIN_32_LEAN_AND_MEAN +#define _WIN32_WINNT 0x0403 +#include <windows.h> +#include <time.h> +#include <dbghelp.h> + +#endif + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +#if defined( _WIN32 ) && !defined( _X360 ) + +#if _MSC_VER >= 1300 + +// MiniDumpWriteDump() function declaration (so we can just get the function directly from windows) +typedef BOOL (WINAPI *MINIDUMPWRITEDUMP) + ( + HANDLE hProcess, + DWORD dwPid, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam + ); + + +// true if we're currently writing a minidump caused by an assert +static bool g_bWritingNonfatalMinidump = false; +// counter used to make sure minidump names are unique +static int g_nMinidumpsWritten = 0; + +//----------------------------------------------------------------------------- +// Purpose: Creates a new file and dumps the exception info into it +// Input : uStructuredExceptionCode - windows exception code, unused. +// pExceptionInfo - call stack. +// minidumpType - type of minidump to write. +// ptchMinidumpFileNameBuffer - if not-NULL points to a writable tchar buffer +// of length at least _MAX_PATH to contain the name +// of the written minidump file on return. +//----------------------------------------------------------------------------- +bool WriteMiniDumpUsingExceptionInfo( + unsigned int uStructuredExceptionCode, + ExceptionInfo_t * pExceptionInfo, + uint32 minidumpType, + tchar *ptchMinidumpFileNameBuffer /* = NULL */ + ) +{ + if ( ptchMinidumpFileNameBuffer ) + { + *ptchMinidumpFileNameBuffer = tchar( 0 ); + } + + // get the function pointer directly so that we don't have to include the .lib, and that + // we can easily change it to using our own dll when this code is used on win98/ME/2K machines + HMODULE hDbgHelpDll = ::LoadLibrary( "DbgHelp.dll" ); + if ( !hDbgHelpDll ) + return false; + + bool bReturnValue = false; + MINIDUMPWRITEDUMP pfnMiniDumpWrite = (MINIDUMPWRITEDUMP) ::GetProcAddress( hDbgHelpDll, "MiniDumpWriteDump" ); + + if ( pfnMiniDumpWrite ) + { + // create a unique filename for the minidump based on the current time and module name + struct tm curtime; + Plat_GetLocalTime( &curtime ); + ++g_nMinidumpsWritten; + + // strip off the rest of the path from the .exe name + tchar rgchModuleName[MAX_PATH]; +#ifdef TCHAR_IS_WCHAR + ::GetModuleFileNameW( NULL, rgchModuleName, sizeof(rgchModuleName) / sizeof(tchar) ); +#else + ::GetModuleFileName( NULL, rgchModuleName, sizeof(rgchModuleName) / sizeof(tchar) ); +#endif + tchar *pch = _tcsrchr( rgchModuleName, '.' ); + if ( pch ) + { + *pch = 0; + } + pch = _tcsrchr( rgchModuleName, '\\' ); + if ( pch ) + { + // move past the last slash + pch++; + } + else + { + pch = _T("unknown"); + } + + + // can't use the normal string functions since we're in tier0 + tchar rgchFileName[MAX_PATH]; + _sntprintf( rgchFileName, sizeof(rgchFileName) / sizeof(tchar), + _T("%s_%s_%d%.2d%2d%.2d%.2d%.2d_%d.mdmp"), + pch, + g_bWritingNonfatalMinidump ? "assert" : "crash", + curtime.tm_year + 1900, /* Year less 2000 */ + curtime.tm_mon + 1, /* month (0 - 11 : 0 = January) */ + curtime.tm_mday, /* day of month (1 - 31) */ + curtime.tm_hour, /* hour (0 - 23) */ + curtime.tm_min, /* minutes (0 - 59) */ + curtime.tm_sec, /* seconds (0 - 59) */ + g_nMinidumpsWritten // ensures the filename is unique + ); + + BOOL bMinidumpResult = FALSE; +#ifdef TCHAR_IS_WCHAR + HANDLE hFile = ::CreateFileW( rgchFileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); +#else + HANDLE hFile = ::CreateFile( rgchFileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); +#endif + + if ( hFile ) + { + // dump the exception information into the file + _MINIDUMP_EXCEPTION_INFORMATION ExInfo; + ExInfo.ThreadId = ::GetCurrentThreadId(); + ExInfo.ExceptionPointers = (PEXCEPTION_POINTERS)pExceptionInfo; + ExInfo.ClientPointers = FALSE; + + bMinidumpResult = (*pfnMiniDumpWrite)( ::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, (MINIDUMP_TYPE)minidumpType, &ExInfo, NULL, NULL ); + ::CloseHandle( hFile ); + + if ( bMinidumpResult ) + { + bReturnValue = true; + + if ( ptchMinidumpFileNameBuffer ) + { + // Copy the file name from "pSrc = rgchFileName" into "pTgt = ptchMinidumpFileNameBuffer" + tchar *pTgt = ptchMinidumpFileNameBuffer; + tchar const *pSrc = rgchFileName; + while ( ( *( pTgt ++ ) = *( pSrc ++ ) ) != tchar( 0 ) ) + continue; + } + } + + // fall through to trying again + } + + // mark any failed minidump writes by renaming them + if ( !bMinidumpResult ) + { + tchar rgchFailedFileName[MAX_PATH]; + _sntprintf( rgchFailedFileName, sizeof(rgchFailedFileName) / sizeof(tchar), "(failed)%s", rgchFileName ); + rename( rgchFileName, rgchFailedFileName ); + } + } + + ::FreeLibrary( hDbgHelpDll ); + + // call the log flush function if one is registered to try to flush any logs + //CallFlushLogFunc(); + + return bReturnValue; +} + + +void InternalWriteMiniDumpUsingExceptionInfo( unsigned int uStructuredExceptionCode, ExceptionInfo_t * pExceptionInfo ) +{ + // First try to write it with all the indirectly referenced memory (ie: a large file). + // If that doesn't work, then write a smaller one. + uint32 iType = MINIDUMP_WithDataSegs | MINIDUMP_WithIndirectlyReferencedMemory; + if ( !WriteMiniDumpUsingExceptionInfo( uStructuredExceptionCode, pExceptionInfo, iType ) ) + { + iType = MINIDUMP_WithDataSegs; + WriteMiniDumpUsingExceptionInfo( uStructuredExceptionCode, pExceptionInfo, iType ); + } +} + +// minidump function to use +static FnMiniDump g_pfnWriteMiniDump = InternalWriteMiniDumpUsingExceptionInfo; + +//----------------------------------------------------------------------------- +// Purpose: Set a function to call which will write our minidump, overriding +// the default function +// Input : pfn - Pointer to minidump function to set +// Output : Previously set function +//----------------------------------------------------------------------------- +FnMiniDump SetMiniDumpFunction( FnMiniDump pfn ) +{ + FnMiniDump pfnTemp = g_pfnWriteMiniDump; + g_pfnWriteMiniDump = pfn; + return pfnTemp; +} + + +//----------------------------------------------------------------------------- +// Unhandled exceptions +//----------------------------------------------------------------------------- +static FnMiniDump g_UnhandledExceptionFunction; +static LONG STDCALL ValveUnhandledExceptionFilter( _EXCEPTION_POINTERS* pExceptionInfo ) +{ + uint uStructuredExceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode; + g_UnhandledExceptionFunction( uStructuredExceptionCode, (ExceptionInfo_t*)pExceptionInfo ); + return EXCEPTION_CONTINUE_SEARCH; +} + +void MinidumpSetUnhandledExceptionFunction( FnMiniDump pfn ) +{ + g_UnhandledExceptionFunction = pfn; + SetUnhandledExceptionFilter( ValveUnhandledExceptionFilter ); +} + + +//----------------------------------------------------------------------------- +// Purpose: writes out a minidump from the current process +//----------------------------------------------------------------------------- +typedef void (*FnMiniDumpInternal_t)( unsigned int uStructuredExceptionCode, _EXCEPTION_POINTERS * pExceptionInfo ); + +void WriteMiniDump() +{ + // throw an exception so we can catch it and get the stack info + g_bWritingNonfatalMinidump = true; + __try + { + ::RaiseException + ( + 0, // dwExceptionCode + EXCEPTION_NONCONTINUABLE, // dwExceptionFlags + 0, // nNumberOfArguments, + NULL // const ULONG_PTR* lpArguments + ); + + // Never get here (non-continuable exception) + } + // Write the minidump from inside the filter (GetExceptionInformation() is only + // valid in the filter) + __except ( g_pfnWriteMiniDump( 0, (ExceptionInfo_t*)GetExceptionInformation() ), EXCEPTION_EXECUTE_HANDLER ) + { + } + g_bWritingNonfatalMinidump = false; +} + +PLATFORM_OVERLOAD bool g_bInException = false; +#include <eh.h> + +//----------------------------------------------------------------------------- +// Purpose: Catches and writes out any exception throw by the specified function +//----------------------------------------------------------------------------- +void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] ) +{ + if ( Plat_IsInDebugSession() ) + { + // don't mask exceptions when running in the debugger + pfn( argc, argv ); + } + else + { + try + { +#pragma warning(push) +#pragma warning(disable : 4535) // warning C4535: calling _set_se_translator() requires /EHa + _set_se_translator( (FnMiniDumpInternal_t)g_pfnWriteMiniDump ); +#pragma warning(pop) + pfn( argc, argv ); + } + catch (...) + { + g_bInException = true; + Log_Msg( LOG_CONSOLE, _T("Fatal exception caught, minidump written\n") ); + // handle everything and just quit, we've already written out our minidump + } + } +} + +#else + +PLATFORM_INTERFACE void WriteMiniDump() +{ +} + +PLATFORM_INTERFACE void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] ) +{ + pfn( argc, argv ); +} + +#endif +#elif defined(_X360 ) +PLATFORM_INTERFACE void WriteMiniDump() +{ +#if !defined( _CERT ) + DmCrashDump(false); +#endif +} + +#else // !_WIN32 + +PLATFORM_INTERFACE void WriteMiniDump() +{ +} + +PLATFORM_INTERFACE void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] ) +{ + pfn( argc, argv ); +} + +#endif diff --git a/external/vpc/tier0/pch_tier0.cpp b/external/vpc/tier0/pch_tier0.cpp new file mode 100644 index 0000000..1cf6ecc --- /dev/null +++ b/external/vpc/tier0/pch_tier0.cpp @@ -0,0 +1,11 @@ +//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + + +#include "pch_tier0.h" + + diff --git a/external/vpc/tier0/pch_tier0.h b/external/vpc/tier0/pch_tier0.h new file mode 100644 index 0000000..6156ad4 --- /dev/null +++ b/external/vpc/tier0/pch_tier0.h @@ -0,0 +1,49 @@ +//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//===========================================================================// + + +#if defined( PLATFORM_WINDOWS_PC ) +#define WIN32_LEAN_AND_MEAN +#define _WIN32_WINNT 0x0403 +#include <windows.h> +#elif defined( _PS3 ) +#include <cellstatus.h> +#include <sys/prx.h> +#endif + + + +#include "tier0/platform.h" + +// First include standard libraries +#include "tier0/valve_off.h" +#include <assert.h> +#include <stdio.h> +#include <ctype.h> +#include <math.h> +#include <ctype.h> +#include <limits.h> + +#include <stddef.h> +#ifdef PLATFORM_POSIX +#include <unistd.h> +#include <ctype.h> +#include <limits.h> +#define _MAX_PATH PATH_MAX +#endif + +#include "tier0/valve_on.h" + +#include "tier0/basetypes.h" +#include "tier0/dbgflag.h" +#include "tier0/dbg.h" +#ifdef STEAM +#include "tier0/memhook.h" +#endif +#include "tier0/validator.h" +#include "tier0/fasttimer.h" diff --git a/external/vpc/tier0/platform.cpp b/external/vpc/tier0/platform.cpp new file mode 100644 index 0000000..15cb1f6 --- /dev/null +++ b/external/vpc/tier0/platform.cpp @@ -0,0 +1,559 @@ +//===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "pch_tier0.h" +#include <time.h> + +#if defined(_WIN32) && !defined(_X360) +#define WINDOWS_LEAN_AND_MEAN +#define _WIN32_WINNT 0x0403 +#include <windows.h> +#endif +#include <errno.h> +#include <assert.h> +#include "tier0/platform.h" +#if defined( _X360 ) +#include "xbox/xbox_console.h" +#endif +#include "tier0/threadtools.h" + +#include "tier0/memalloc.h" + +#if defined( _PS3 ) +#include <cell/fios/fios_common.h> +#include <cell/fios/fios_memory.h> +#include <cell/fios/fios_configuration.h> +#include <sys/process.h> + +#if !defined(_CERT) +#include "sn/LibSN.h" +#endif + +/* +#include <sys/types.h> +#include <sys/process.h> +#include <sys/prx.h> + +#include <sysutil/sysutil_syscache.h> + +#include <cell/sysmodule.h> +*/ +#include <cell/fios/fios_time.h> +#endif // _PS3 + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifdef _WIN32 +static LARGE_INTEGER g_PerformanceFrequency; +static LARGE_INTEGER g_MSPerformanceFrequency; +static LARGE_INTEGER g_ClockStart; +static bool s_bTimeInitted; +#endif + +// Benchmark mode uses this heavy-handed method +static bool g_bBenchmarkMode = false; +#ifdef _WIN32 +static double g_FakeBenchmarkTime = 0; +static double g_FakeBenchmarkTimeInc = 1.0 / 66.0; +#endif + +static CThreadFastMutex g_LocalTimeMutex; + + +#ifdef _WIN32 +static void InitTime() +{ + if( !s_bTimeInitted ) + { + s_bTimeInitted = true; + QueryPerformanceFrequency(&g_PerformanceFrequency); + g_MSPerformanceFrequency.QuadPart = g_PerformanceFrequency.QuadPart / 1000; + QueryPerformanceCounter(&g_ClockStart); + } +} +#endif + +bool Plat_IsInBenchmarkMode() +{ + return g_bBenchmarkMode; +} + +void Plat_SetBenchmarkMode( bool bBenchmark ) +{ + g_bBenchmarkMode = bBenchmark; +} + +#ifdef _PS3 +cell::fios::abstime_t g_fiosLaunchTime = 0; +#endif + + +double Plat_FloatTime() +{ +#ifdef _WIN32 + if (! s_bTimeInitted ) + InitTime(); + if ( g_bBenchmarkMode ) + { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return g_FakeBenchmarkTime; + } + + LARGE_INTEGER CurrentTime; + + QueryPerformanceCounter( &CurrentTime ); + + double fRawSeconds = (double)( CurrentTime.QuadPart - g_ClockStart.QuadPart ) / (double)(g_PerformanceFrequency.QuadPart); + + return fRawSeconds; +#else + return cell::fios::FIOSAbstimeToMicroseconds( cell::fios::FIOSGetCurrentTime() - g_fiosLaunchTime ) * 1e-6; +#endif +} + + +uint32 Plat_MSTime() +{ +#ifdef _WIN32 + if (! s_bTimeInitted ) + InitTime(); + if ( g_bBenchmarkMode ) + { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return (uint32)(g_FakeBenchmarkTime * 1000.0); + } + + LARGE_INTEGER CurrentTime; + + QueryPerformanceCounter( &CurrentTime ); + + return (uint32) ( ( CurrentTime.QuadPart - g_ClockStart.QuadPart ) / g_MSPerformanceFrequency.QuadPart); +#elif defined(_PS3) + return (uint32) cell::fios::FIOSAbstimeToMilliseconds( cell::fios::FIOSGetCurrentTime() - g_fiosLaunchTime ); +#else + #error +#endif +} + +uint64 Timer_GetTimeUS() +{ +#ifdef _PS3 + return cell::fios::FIOSAbstimeToMicroseconds( cell::fios::FIOSGetCurrentTime() - g_fiosLaunchTime ); +#else + return uint64( Plat_FloatTime() * 1000000 ); +#endif +} + +uint64 Plat_GetClockStart() +{ +#if defined( _WIN32 ) + if ( !s_bTimeInitted ) + InitTime(); + + return g_ClockStart.QuadPart; +#elif defined( _PS3 ) + return g_fiosLaunchTime; +#else + return 0; +#endif +} + +void Plat_GetLocalTime( struct tm *pNow ) +{ + // We just provide a wrapper on this function so we can protect access to time() everywhere. + time_t ltime; + time( <ime ); + + Plat_ConvertToLocalTime( ltime, pNow ); +} + +void Plat_ConvertToLocalTime( uint64 nTime, struct tm *pNow ) +{ + // Since localtime() returns a global, we need to protect against multiple threads stomping it. + g_LocalTimeMutex.Lock(); + + time_t ltime = (time_t)nTime; + tm *pTime = localtime( <ime ); + if ( pTime ) + *pNow = *pTime; + else + memset( pNow, 0, sizeof( *pNow ) ); + + g_LocalTimeMutex.Unlock(); +} + +void Plat_GetTimeString( struct tm *pTime, char *pOut, int nMaxBytes ) +{ + g_LocalTimeMutex.Lock(); + + char *pStr = asctime( pTime ); + strncpy( pOut, pStr, nMaxBytes ); + pOut[nMaxBytes-1] = 0; + + g_LocalTimeMutex.Unlock(); +} + + +void Plat_gmtime( uint64 nTime, struct tm *pTime ) +{ + time_t tmtTime = nTime; +#ifdef _PS3 + struct tm * tmp = gmtime( &tmtTime ); + * pTime = * tmp; +#else + gmtime_s( pTime, &tmtTime ); +#endif +} + +time_t Plat_timegm( struct tm *timeptr ) +{ +#ifndef _GAMECONSOLE + return _mkgmtime( timeptr ); +#else + int *pnCrashHereBecauseConsolesDontSupportMkGmTime = 0; + *pnCrashHereBecauseConsolesDontSupportMkGmTime = 0; + return 0; +#endif +} + +void Plat_GetModuleFilename( char *pOut, int nMaxBytes ) +{ +#ifdef PLATFORM_WINDOWS_PC + GetModuleFileName( NULL, pOut, nMaxBytes ); + if ( GetLastError() != ERROR_SUCCESS ) + Error( "Plat_GetModuleFilename: The buffer given is too small (%d bytes).", nMaxBytes ); +#elif PLATFORM_X360 + pOut[0] = 0x00; // return null string on Xbox 360 +#else + // We shouldn't need this on POSIX. + Assert( false ); + pOut[0] = 0x00; // Null the returned string in release builds +#endif +} + +void Plat_ExitProcess( int nCode ) +{ +#if defined( _WIN32 ) && !defined( _X360 ) + // We don't want global destructors in our process OR in any DLL to get executed. + // _exit() avoids calling global destructors in our module, but not in other DLLs. + TerminateProcess( GetCurrentProcess(), nCode ); +#elif defined(_PS3) + // We do not use this path to exit on PS3 (naturally), rather we want a clear crash: + int *x = NULL; *x = 1; +#else + _exit( nCode ); +#endif +} + +void GetCurrentDate( int *pDay, int *pMonth, int *pYear ) +{ + struct tm long_time; + Plat_GetLocalTime( &long_time ); + + *pDay = long_time.tm_mday; + *pMonth = long_time.tm_mon + 1; + *pYear = long_time.tm_year + 1900; +} + +// Wraps the thread-safe versions of asctime. buf must be at least 26 bytes +char *Plat_asctime( const struct tm *tm, char *buf, size_t bufsize ) +{ +#ifdef _PS3 + snprintf( buf, bufsize, "%s", asctime(tm) ); + return buf; +#else + if ( EINVAL == asctime_s( buf, bufsize, tm ) ) + return NULL; + else + return buf; +#endif +} + + +// Wraps the thread-safe versions of ctime. buf must be at least 26 bytes +char *Plat_ctime( const time_t *timep, char *buf, size_t bufsize ) +{ +#ifdef _PS3 + snprintf( buf, bufsize, "%s", ctime( timep ) ); + return buf; +#else + if ( EINVAL == ctime_s( buf, bufsize, timep ) ) + return NULL; + else + return buf; +#endif +} + + +// Wraps the thread-safe versions of gmtime +struct tm *Plat_gmtime( const time_t *timep, struct tm *result ) +{ +#ifdef _PS3 + *result = *gmtime( timep ); + return result; +#else + if ( EINVAL == gmtime_s( result, timep ) ) + return NULL; + else + return result; +#endif +} + + +// Wraps the thread-safe versions of localtime +struct tm *Plat_localtime( const time_t *timep, struct tm *result ) +{ +#ifdef _PS3 + *result = *localtime( timep ); + return result; +#else + if ( EINVAL == localtime_s( result, timep ) ) + return NULL; + else + return result; +#endif +} + +bool vtune( bool resume ) +{ +#if IS_WINDOWS_PC + static bool bInitialized = false; + static void (__cdecl *VTResume)(void) = NULL; + static void (__cdecl *VTPause) (void) = NULL; + + // Grab the Pause and Resume function pointers from the VTune DLL the first time through: + if( !bInitialized ) + { + bInitialized = true; + + HINSTANCE pVTuneDLL = LoadLibrary( "vtuneapi.dll" ); + + if( pVTuneDLL ) + { + VTResume = (void(__cdecl *)())GetProcAddress( pVTuneDLL, "VTResume" ); + VTPause = (void(__cdecl *)())GetProcAddress( pVTuneDLL, "VTPause" ); + } + } + + // Call the appropriate function, as indicated by the argument: + if( resume && VTResume ) + { + VTResume(); + return true; + + } + else if( !resume && VTPause ) + { + VTPause(); + return true; + } +#endif + return false; +} + +bool Plat_IsInDebugSession() +{ +#if defined( _X360 ) + return (XBX_IsDebuggerPresent() != 0); +#elif defined( _WIN32 ) + return (IsDebuggerPresent() != 0); +#elif defined( _PS3 ) && !defined(_CERT) + return snIsDebuggerPresent(); +#else + return false; +#endif +} + +void Plat_DebugString( const char * psz ) +{ +#ifdef _CERT + return; // do nothing! +#endif + +#if defined( _X360 ) + XBX_OutputDebugString( psz ); +#elif defined( _WIN32 ) + ::OutputDebugStringA( psz ); +#elif defined(_PS3) + printf("%s",psz); +#else + // do nothing? +#endif +} + + +#if defined( PLATFORM_WINDOWS_PC ) +void Plat_MessageBox( const char *pTitle, const char *pMessage ) +{ + MessageBox( NULL, pMessage, pTitle, MB_OK ); +} +#endif + +PlatOSVersion_t Plat_GetOSVersion() +{ +#ifdef PLATFORM_WINDOWS_PC + OSVERSIONINFO info; + info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + if ( GetVersionEx( &info ) ) + return (PlatOSVersion_t)info.dwMajorVersion; + return PLAT_OS_VERSION_UNKNOWN; +#elif defined( PLATFORM_X360 ) + return PLAT_OS_VERSION_XBOX360; +#else + return PLAT_OS_VERSION_UNKNOWN; +#endif +} + +#if defined( PLATFORM_PS3 ) +//copied from platform_posix.cpp +static char g_CmdLine[ 2048 ] = ""; +PLATFORM_INTERFACE void Plat_SetCommandLine( const char *cmdLine ) +{ + strncpy( g_CmdLine, cmdLine, sizeof(g_CmdLine) ); + g_CmdLine[ sizeof(g_CmdLine) -1 ] = 0; +} +#endif + +PLATFORM_INTERFACE const tchar *Plat_GetCommandLine() +{ +#if defined( _PS3 ) +#pragma message("Plat_GetCommandLine() not implemented on PS3") // **** + return g_CmdLine; +#elif defined( TCHAR_IS_WCHAR ) + return GetCommandLineW(); +#else + return GetCommandLine(); +#endif +} + +PLATFORM_INTERFACE const char *Plat_GetCommandLineA() +{ +#if defined( _PS3 ) +#pragma message("Plat_GetCommandLineA() not implemented on PS3") // **** + return g_CmdLine; +#else + return GetCommandLineA(); +#endif +} + + +//----------------------------------------------------------------------------- +// Dynamically load a function +//----------------------------------------------------------------------------- +#ifdef PLATFORM_WINDOWS + +void *Plat_GetProcAddress( const char *pszModule, const char *pszName ) +{ + HMODULE hModule = ::LoadLibrary( pszModule ); + return ( hModule ) ? ::GetProcAddress( hModule, pszName ) : NULL; +} + +#endif + + + +// -------------------------------------------------------------------------------------------------- // +// Memory stuff. +// +// DEPRECATED. Still here to support binary back compatability of tier0.dll +// +// -------------------------------------------------------------------------------------------------- // +#ifndef _X360 +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + +typedef void (*Plat_AllocErrorFn)( unsigned long size ); + +void Plat_DefaultAllocErrorFn( unsigned long size ) +{ +} + +Plat_AllocErrorFn g_AllocError = Plat_DefaultAllocErrorFn; +#endif + +#if !defined( _X360 ) && !defined( _PS3 ) + + +CRITICAL_SECTION g_AllocCS; +class CAllocCSInit +{ +public: + CAllocCSInit() + { + InitializeCriticalSection( &g_AllocCS ); + } +} g_AllocCSInit; + + +PLATFORM_INTERFACE void* Plat_Alloc( unsigned long size ) +{ + EnterCriticalSection( &g_AllocCS ); +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + void *pRet = MemAlloc_Alloc( size ); +#else + void *pRet = malloc( size ); +#endif + LeaveCriticalSection( &g_AllocCS ); + if ( pRet ) + { + return pRet; + } + else + { +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + g_AllocError( size ); +#endif + return 0; + } +} + + +PLATFORM_INTERFACE void* Plat_Realloc( void *ptr, unsigned long size ) +{ + EnterCriticalSection( &g_AllocCS ); +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + void *pRet = g_pMemAlloc->Realloc( ptr, size ); +#else + void *pRet = realloc( ptr, size ); +#endif + LeaveCriticalSection( &g_AllocCS ); + if ( pRet ) + { + return pRet; + } + else + { +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + g_AllocError( size ); +#endif + return 0; + } +} + + +PLATFORM_INTERFACE void Plat_Free( void *ptr ) +{ + EnterCriticalSection( &g_AllocCS ); +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + g_pMemAlloc->Free( ptr ); +#else + free( ptr ); +#endif + LeaveCriticalSection( &g_AllocCS ); +} + +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) +PLATFORM_INTERFACE void Plat_SetAllocErrorFn( Plat_AllocErrorFn fn ) +{ + g_AllocError = fn; +} +#endif + +#endif + + +#endif
\ No newline at end of file diff --git a/external/vpc/tier0/platform_posix.cpp b/external/vpc/tier0/platform_posix.cpp new file mode 100644 index 0000000..90a8c76 --- /dev/null +++ b/external/vpc/tier0/platform_posix.cpp @@ -0,0 +1,426 @@ +//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "pch_tier0.h" +#include "tier0/platform.h" +#include "tier0/memalloc.h" +#include "tier0/dbg.h" +#include "tier0/threadtools.h" + +#include <sys/time.h> +#include <unistd.h> + +#ifdef OSX +#include <sys/sysctl.h> +#include <mach/mach.h> +#include <mach/mach_time.h> +#endif + + +static bool g_bBenchmarkMode = false; +static double g_FakeBenchmarkTime = 0; +static double g_FakeBenchmarkTimeInc = 1.0 / 66.0; + + +bool Plat_IsInBenchmarkMode() +{ + return g_bBenchmarkMode; +} + +void Plat_SetBenchmarkMode( bool bBenchmark ) +{ + g_bBenchmarkMode = bBenchmark; +} + + + +#ifdef OSX + +static uint64 start_time = 0; +static mach_timebase_info_data_t sTimebaseInfo; +static double conversion = 0.0; + +void InitTime() +{ + start_time = mach_absolute_time(); + mach_timebase_info(&sTimebaseInfo); + conversion = 1e-9 * (double) sTimebaseInfo.numer / (double) sTimebaseInfo.denom; +} + +uint64 Plat_GetClockStart() +{ + if ( !start_time ) + { + InitTime(); + } + + return start_time * conversion; +} + +double Plat_FloatTime() +{ + if ( g_bBenchmarkMode ) + { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return g_FakeBenchmarkTime; + } + + if ( !start_time ) + { + InitTime(); + } + + uint64 now = mach_absolute_time(); + + return ( now - start_time ) * conversion; +} +#else + +static int secbase = 0; + +void InitTime( struct timeval &tp ) +{ + secbase = tp.tv_sec; +} + +uint64 Plat_GetClockStart() +{ + if ( !secbase ) + { + struct timeval tp; + gettimeofday( &tp, NULL ); + InitTime( tp ); + } + + return secbase; +} + + +double Plat_FloatTime() +{ + if ( g_bBenchmarkMode ) + { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return g_FakeBenchmarkTime; + } + + struct timeval tp; + + gettimeofday( &tp, NULL ); + + if ( !secbase ) + { + InitTime( tp ); + return ( tp.tv_usec / 1000000.0 ); + } + + return (( tp.tv_sec - secbase ) + tp.tv_usec / 1000000.0 ); +} +#endif + + +uint32 Plat_MSTime() +{ + if ( g_bBenchmarkMode ) + { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return (unsigned long)(g_FakeBenchmarkTime * 1000.0); + } + + struct timeval tp; + static int secbase = 0; + + gettimeofday( &tp, NULL ); + + if ( !secbase ) + { + secbase = tp.tv_sec; + return ( tp.tv_usec / 1000.0 ); + } + + return (unsigned long)(( tp.tv_sec - secbase )*1000.0 + tp.tv_usec / 1000.0 ); + +} + +// Wraps the thread-safe versions of asctime. buf must be at least 26 bytes +char *Plat_asctime( const struct tm *tm, char *buf, size_t bufsize ) +{ + return asctime_r( tm, buf ); +} + +// Wraps the thread-safe versions of ctime. buf must be at least 26 bytes +char *Plat_ctime( const time_t *timep, char *buf, size_t bufsize ) +{ + return ctime_r( timep, buf ); +} + +// Wraps the thread-safe versions of gmtime +struct tm *Plat_gmtime( const time_t *timep, struct tm *result ) +{ + return gmtime_r( timep, result ); +} + +time_t Plat_timegm( struct tm *timeptr ) +{ + return timegm( timeptr ); +} + +// Wraps the thread-safe versions of localtime +struct tm *Plat_localtime( const time_t *timep, struct tm *result ) +{ + return localtime_r( timep, result ); +} + +bool vtune( bool resume ) +{ +} + + +// -------------------------------------------------------------------------------------------------- // +// Memory stuff. +// -------------------------------------------------------------------------------------------------- // + +PLATFORM_INTERFACE void Plat_DefaultAllocErrorFn( unsigned long size ) +{ +} + +typedef void (*Plat_AllocErrorFn)( unsigned long size ); +Plat_AllocErrorFn g_AllocError = Plat_DefaultAllocErrorFn; + +PLATFORM_INTERFACE void* Plat_Alloc( unsigned long size ) +{ + void *pRet = g_pMemAlloc->Alloc( size ); + if ( pRet ) + { + return pRet; + } + else + { + g_AllocError( size ); + return 0; + } +} + + +PLATFORM_INTERFACE void* Plat_Realloc( void *ptr, unsigned long size ) +{ + void *pRet = g_pMemAlloc->Realloc( ptr, size ); + if ( pRet ) + { + return pRet; + } + else + { + g_AllocError( size ); + return 0; + } +} + + +PLATFORM_INTERFACE void Plat_Free( void *ptr ) +{ +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + g_pMemAlloc->Free( ptr ); +#else + free( ptr ); +#endif +} + + +PLATFORM_INTERFACE void Plat_SetAllocErrorFn( Plat_AllocErrorFn fn ) +{ + g_AllocError = fn; +} + +static char g_CmdLine[ 2048 ]; +PLATFORM_INTERFACE void Plat_SetCommandLine( const char *cmdLine ) +{ + strncpy( g_CmdLine, cmdLine, sizeof(g_CmdLine) ); + g_CmdLine[ sizeof(g_CmdLine) -1 ] = 0; +} + +PLATFORM_INTERFACE void Plat_SetCommandLineArgs( char **argv, int argc ) +{ + g_CmdLine[0] = 0; + for ( int i = 0; i < argc; i++ ) + { + strncat( g_CmdLine, argv[i], sizeof(g_CmdLine) - strlen(g_CmdLine) ); + } + + g_CmdLine[ sizeof(g_CmdLine) -1 ] = 0; +} + + +PLATFORM_INTERFACE const tchar *Plat_GetCommandLine() +{ + return g_CmdLine; +} + +PLATFORM_INTERFACE bool Is64BitOS() +{ +#if defined OSX + return true; +#elif defined LINUX + FILE *pp = popen( "uname -m", "r" ); + if ( pp != NULL ) + { + char rgchArchString[256]; + fgets( rgchArchString, sizeof( rgchArchString ), pp ); + pclose( pp ); + if ( !strncasecmp( rgchArchString, "x86_64", strlen( "x86_64" ) ) ) + return true; + } +#else + Assert( !"implement Is64BitOS" ); +#endif + return false; +} + + +bool Plat_IsInDebugSession() +{ +#if defined(OSX) + int mib[4]; + struct kinfo_proc info; + size_t size; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + size = sizeof(info); + info.kp_proc.p_flag = 0; + sysctl(mib,4,&info,&size,NULL,0); + bool result = ((info.kp_proc.p_flag & P_TRACED) == P_TRACED); + return result; +#elif defined(LINUX) + char s[256]; + snprintf(s, 256, "/proc/%d/cmdline", getppid()); + FILE * fp = fopen(s, "r"); + if (fp != NULL) + { + fread(s, 256, 1, fp); + fclose(fp); + return (0 == strncmp(s, "gdb", 3)); + } + return false; +#endif +} + + + +void Plat_ExitProcess( int nCode ) +{ + _exit( nCode ); +} + +static int s_nWatchDogTimerTimeScale = 0; +static bool s_bInittedWD = false; + + +static void InitWatchDogTimer( void ) +{ + if( !strstr( g_CmdLine, "-nowatchdog" ) ) + { +#ifdef _DEBUG + s_nWatchDogTimerTimeScale = 10; // debug is slow +#else + s_nWatchDogTimerTimeScale = 1; +#endif + + } + +} + +// watchdog timer support +void BeginWatchdogTimer( int nSecs ) +{ + if (! s_bInittedWD ) + { + s_bInittedWD = true; + InitWatchDogTimer(); + } + nSecs *= s_nWatchDogTimerTimeScale; + nSecs = MIN( nSecs, 5 * 60 ); // no more than 5 minutes no matter what + if ( nSecs ) + alarm( nSecs ); +} + +void EndWatchdogTimer( void ) +{ + alarm( 0 ); +} + +static CThreadMutex g_LocalTimeMutex; + + +void Plat_GetLocalTime( struct tm *pNow ) +{ + // We just provide a wrapper on this function so we can protect access to time() everywhere. + time_t ltime; + time( <ime ); + + Plat_ConvertToLocalTime( ltime, pNow ); +} + +void Plat_ConvertToLocalTime( uint64 nTime, struct tm *pNow ) +{ + // Since localtime() returns a global, we need to protect against multiple threads stomping it. + g_LocalTimeMutex.Lock(); + + time_t ltime = (time_t)nTime; + tm *pTime = localtime( <ime ); + if ( pTime ) + *pNow = *pTime; + else + memset( pNow, 0, sizeof( *pNow ) ); + + g_LocalTimeMutex.Unlock(); +} + +void Plat_GetTimeString( struct tm *pTime, char *pOut, int nMaxBytes ) +{ + g_LocalTimeMutex.Lock(); + + char *pStr = asctime( pTime ); + strncpy( pOut, pStr, nMaxBytes ); + pOut[nMaxBytes-1] = 0; + + g_LocalTimeMutex.Unlock(); +} + + +void Plat_gmtime( uint64 nTime, struct tm *pTime ) +{ + time_t tmtTime = nTime; + struct tm * tmp = gmtime( &tmtTime ); + * pTime = * tmp; +} + +#ifdef LINUX +size_t ApproximateProcessMemoryUsage( void ) +{ + int nRet = 0; + FILE *pFile = fopen( "/proc/self/statm", "r" ); + if ( pFile ) + { + int nSize, nTotalProgramSize, nResident, nResidentSetSize, nShare, nSharedPagesTotal, nDummy0; + if ( fscanf( pFile, "%d %d %d %d %d %d %d", &nSize, &nTotalProgramSize, &nResident, &nResidentSetSize, &nShare, &nSharedPagesTotal, &nDummy0 ) ) + { + nRet = 4096 * nSize; + } + fclose( pFile ); + } + return nRet; +} +#else + +size_t ApproximateProcessMemoryUsage( void ) +{ + return 0; +} + +#endif diff --git a/external/vpc/tier0/pme.cpp b/external/vpc/tier0/pme.cpp new file mode 100644 index 0000000..23a5442 --- /dev/null +++ b/external/vpc/tier0/pme.cpp @@ -0,0 +1,162 @@ +//===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +//===========================================================================// + +#include "tier0/platform.h" + +#include "pch_tier0.h" +#define WINDOWS_LEAN_AND_MEAN +#include <windows.h> + +#pragma warning( disable : 4530 ) // warning: exception handler -GX option + +#include "tier0/platform.h" +#include "tier0/vprof.h" +#include "tier0/pmelib.h" +#include "tier0/l2cache.h" +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Purpose: Initialization +//----------------------------------------------------------------------------- +void InitPME( void ) +{ + bool bInit = false; + + PME *pPME = PME::Instance(); + if ( pPME ) + { + if ( pPME->GetVendor() != INTEL ) + return; + + if ( pPME->GetProcessorFamily() != PENTIUM4_FAMILY ) + return; + + pPME->SetProcessPriority( ProcessPriorityHigh ); + + bInit = true; + + DevMsg( 1, _T("PME Initialized.\n") ); + } + else + { + DevMsg( 1, _T("PME Uninitialized.\n") ); + } + +#ifdef VPROF_ENABLED + g_VProfCurrentProfile.PMEInitialized( bInit ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Shutdown +//----------------------------------------------------------------------------- +void ShutdownPME( void ) +{ + PME *pPME = PME::Instance(); + if ( pPME ) + { + pPME->SetProcessPriority( ProcessPriorityNormal ); + } + +#ifdef VPROF_ENABLED + g_VProfCurrentProfile.PMEInitialized( false ); +#endif +} + +//============================================================================= +// +// CL2Cache Code. +// + +static int s_nCreateCount = 0; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CL2Cache::CL2Cache() +{ + m_nID = s_nCreateCount++; + m_pL2CacheEvent = new P4Event_BSQ_cache_reference; + m_iL2CacheMissCount = 0; + m_i64Start = 0; + m_i64End = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CL2Cache::~CL2Cache() +{ + if ( m_pL2CacheEvent ) + { + delete m_pL2CacheEvent; + m_pL2CacheEvent = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CL2Cache::Start( void ) +{ + if ( m_pL2CacheEvent ) + { + // Set this up to check for L2 cache misses. + m_pL2CacheEvent->eventMask->RD_2ndL_MISS = 1; + + // Set the event mask and set the capture mode. +// m_pL2CacheEvent->SetCaptureMode( USR_Only ); + m_pL2CacheEvent->SetCaptureMode( OS_and_USR ); + + // That's it, now sw capture events + m_pL2CacheEvent->StopCounter(); + m_pL2CacheEvent->ClearCounter(); + + m_pL2CacheEvent->StartCounter(); + m_i64Start = m_pL2CacheEvent->ReadCounter(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CL2Cache::End( void ) +{ + if ( m_pL2CacheEvent ) + { + // Stop the counter and find the delta. + m_i64End = m_pL2CacheEvent->ReadCounter(); + int64 i64Delta = m_i64End - m_i64Start; + m_pL2CacheEvent->StopCounter(); + + // Save the delta for later query. + m_iL2CacheMissCount = ( int )i64Delta; + } +} + +#pragma warning( default : 4530 ) + +#ifdef DBGFLAG_VALIDATE +//----------------------------------------------------------------------------- +// Purpose: Ensure that all of our internal structures are consistent, and +// account for all memory that we've allocated. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void CL2Cache::Validate( CValidator &validator, tchar *pchName ) +{ + validator.Push( _T("CL2Cache"), this, pchName ); + + validator.ClaimMemory( m_pL2CacheEvent ); + + validator.Pop( ); +} +#endif // DBGFLAG_VALIDATE + diff --git a/external/vpc/tier0/pme_posix.cpp b/external/vpc/tier0/pme_posix.cpp new file mode 100644 index 0000000..ff782ce --- /dev/null +++ b/external/vpc/tier0/pme_posix.cpp @@ -0,0 +1,51 @@ +//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "tier0/platform.h" +#include "tier0/vprof.h" +#include "tier0/dbg.h" + +//----------------------------------------------------------------------------- +// Purpose: Initialization +//----------------------------------------------------------------------------- +void InitPME( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Shutdown +//----------------------------------------------------------------------------- +void ShutdownPME( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CL2Cache::CL2Cache() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CL2Cache::~CL2Cache() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CL2Cache::Start( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CL2Cache::End( void ) +{ +} diff --git a/external/vpc/tier0/pmelib.cpp b/external/vpc/tier0/pmelib.cpp new file mode 100644 index 0000000..c4f1aad --- /dev/null +++ b/external/vpc/tier0/pmelib.cpp @@ -0,0 +1,665 @@ +//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// + +#ifdef _WIN32 +#include <windows.h> + +#pragma warning( disable : 4530 ) // warning: exception handler -GX option + +#include "tier0/valve_off.h" +#include "tier0/pmelib.h" +#if _MSC_VER >=1300 +#else +#include "winioctl.h" +#endif +#include "tier0/valve_on.h" + +#include "tier0/ioctlcodes.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +PME* PME::_singleton = 0; + +// Single interface. +PME* PME::Instance() +{ + if (_singleton == 0) + { + _singleton = new PME; + } + return _singleton; +} + +//--------------------------------------------------------------------------- +// Open the device driver and detect the processor +//--------------------------------------------------------------------------- +HRESULT PME::Init( void ) +{ + OSVERSIONINFO OS; + + if ( bDriverOpen ) + return E_DRIVER_ALREADY_OPEN; + + switch( vendor ) + { + case INTEL: + case AMD: + break; + default: + bDriverOpen = FALSE; // not an Intel or Athlon processor so return false + return E_UNKNOWN_CPU_VENDOR; + } + + //----------------------------------------------------------------------- + // Get the operating system version + //----------------------------------------------------------------------- + OS.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); + GetVersionEx( &OS ); + + if ( OS.dwPlatformId == VER_PLATFORM_WIN32_NT ) + { + hFile = CreateFile( // WINDOWS NT + "\\\\.\\GDPERF", + GENERIC_READ, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + } + else + { + hFile = CreateFile( // WINDOWS 95 + "\\\\.\\GDPERF.VXD", + GENERIC_READ, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + } + + if (hFile == INVALID_HANDLE_VALUE ) + return E_CANT_OPEN_DRIVER; + + + bDriverOpen = TRUE; + + + //------------------------------------------------------------------- + // We have successfully opened the device driver, get the family + // of the processor. + //------------------------------------------------------------------- + + + + //------------------------------------------------------------------- + // We need to write to counter 0 on the pro family to enable both + // of the performance counters. We write to both so they start in a + // known state. For the pentium this is not necessary. + //------------------------------------------------------------------- + if (vendor == INTEL && version.Family == PENTIUMPRO_FAMILY) + { + SelectP5P6PerformanceEvent(P6_CLOCK, 0, TRUE, TRUE); + SelectP5P6PerformanceEvent(P6_CLOCK, 1, TRUE, TRUE); + } + + return S_OK; + + +} + + + +//--------------------------------------------------------------------------- +// Close the device driver +//--------------------------------------------------------------------------- +HRESULT PME::Close(void) +{ + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + bDriverOpen = false; + + if (hFile) // if we have no driver handle, return FALSE + { + HRESULT hr = CloseHandle(hFile); + + hFile = NULL; + return hr; + } + else + return E_DRIVER_NOT_OPEN; + + +} + +//--------------------------------------------------------------------------- +// Select the event to monitor with counter 0 +// +HRESULT PME::SelectP5P6PerformanceEvent(uint32 dw_event, uint32 dw_counter, + bool b_user, bool b_kernel) +{ + HRESULT hr = S_OK; + + if (dw_counter>1) // is the counter valid + return E_BAD_COUNTER; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + if ( ((dw_event>>28)&0xF) != (uint32)version.Family) + { + return E_ILLEGAL_OPERATION; // this operation is not for this processor + } + + if ( (((dw_event & 0x300)>>8) & (dw_counter+1)) == 0 ) + { + return E_ILLEGAL_OPERATION; // this operation is not for this counter + } + + switch(version.Family) + { + case PENTIUM_FAMILY: + { + uint64 i64_cesr; + int i_kernel_bit,i_user_bit; + BYTE u1_event = (BYTE)((dw_event & (0x3F0000))>>16); + + if (dw_counter==0) // the kernel and user mode bits depend on + { // counter being used. + i_kernel_bit = 6; + i_user_bit = 7; + } + else + { + i_kernel_bit = 22; + i_user_bit = 23; + } + + ReadMSR(0x11, &i64_cesr); // get current P5 event select (cesr) + + // top 32bits of cesr are not valid so ignore them + i64_cesr &= ((dw_counter == 0)?0xffff0000:0x0000ffff); + WriteMSR(0x11,i64_cesr); // stop the counter + WriteMSR((dw_counter==0)?0x12:0x13,0ui64); // clear the p.counter + + // set the user and kernel mode bits + i64_cesr |= ( b_user?(1<<7):0 ) | ( b_kernel?(1<<6):0 ); + + // is this the special P5 value that signals count clocks?? + if (u1_event == 0x3f) + { + WriteMSR(0x11, i64_cesr|0x100); // Count clocks + } + else + { + WriteMSR(0x11, i64_cesr|u1_event); // Count events + } + + } + break; + + case PENTIUMPRO_FAMILY: + + { + BYTE u1_event = (BYTE)((dw_event & (0xFF0000))>>16); + BYTE u1_mask = (BYTE)((dw_event & 0xFF)); + + // Event select 0 and 1 are identical. + hr = WriteMSR((dw_counter==0)?0x186:0x187, + + + uint64((u1_event | (b_user?(1<<16):0) | (b_kernel?(1<<17):0) | (1<<22) | (1<<18) | (u1_mask<<8)) ) + ); + } + break; + + case PENTIUM4_FAMILY: + // use the p4 path + break; + + default: + return E_UNKNOWN_CPU; + } + + return hr; +} + +//--------------------------------------------------------------------------- +// Read model specific register +//--------------------------------------------------------------------------- +HRESULT PME::ReadMSR(uint32 dw_reg, int64 * pi64_value) +{ + HRESULT hr; + DWORD dw_ret_len; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + hr = DeviceIoControl + ( + hFile, // Handle to device + (DWORD) IOCTL_READ_MSR, // IO Control code for Read + &dw_reg, // Input Buffer to driver. + sizeof(uint32), // Length of input buffer. + pi64_value, // Output Buffer from driver. + sizeof(int64), // Length of output buffer in bytes. + &dw_ret_len, // Bytes placed in output buffer. + NULL // NULL means wait till op. completes + ); + + if (hr == S_OK && dw_ret_len != sizeof(int64)) + hr = E_BAD_DATA; + + return hr; +} + +HRESULT PME::ReadMSR(uint32 dw_reg, uint64 * pi64_value) +{ + HRESULT hr; + DWORD dw_ret_len; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + hr = DeviceIoControl + ( + hFile, // Handle to device + (DWORD) IOCTL_READ_MSR, // IO Control code for Read + &dw_reg, // Input Buffer to driver. + sizeof(uint32), // Length of input buffer. + pi64_value, // Output Buffer from driver. + sizeof(uint64), // Length of output buffer in bytes. + &dw_ret_len, // Bytes placed in output buffer. + NULL // NULL means wait till op. completes + ); + + if (hr == S_OK && dw_ret_len != sizeof(uint64)) + hr = E_BAD_DATA; + + return hr; +} + +//--------------------------------------------------------------------------- +// Write model specific register +//--------------------------------------------------------------------------- +HRESULT PME::WriteMSR(uint32 dw_reg, const int64 & i64_value) +{ + HRESULT hr; + DWORD dw_buffer[3]; + DWORD dw_ret_len; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + dw_buffer[0] = dw_reg; // setup the 12 byte input + *((int64*)(&dw_buffer[1]))= i64_value; + + hr = DeviceIoControl + ( + hFile, // Handle to device + (DWORD) IOCTL_WRITE_MSR, // IO Control code for Read + dw_buffer, // Input Buffer to driver. + 12, // Length of Input buffer + NULL, // Buffer from driver, None for WRMSR + 0, // Length of output buffer in bytes. + &dw_ret_len, // Bytes placed in DataBuffer. + NULL // NULL means wait till op. completes. + ); + + if (hr == S_OK && dw_ret_len != 0) + hr = E_BAD_DATA; + + return hr; +} + + + +HRESULT PME::WriteMSR(uint32 dw_reg, const uint64 & i64_value) +{ + HRESULT hr; + DWORD dw_buffer[3]; + DWORD dw_ret_len; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + dw_buffer[0] = dw_reg; // setup the 12 byte input + *((uint64*)(&dw_buffer[1]))= i64_value; + + hr = DeviceIoControl + ( + hFile, // Handle to device + (DWORD) IOCTL_WRITE_MSR, // IO Control code for Read + dw_buffer, // Input Buffer to driver. + 12, // Length of Input buffer + NULL, // Buffer from driver, None for WRMSR + 0, // Length of output buffer in bytes. + &dw_ret_len, // Bytes placed in DataBuffer. + NULL // NULL means wait till op. completes. + ); + + //E_POINTER + if (hr == S_OK && dw_ret_len != 0) + hr = E_BAD_DATA; + + return hr; +} + + + + + + + + + + + + + +#pragma hdrstop + + + + +//--------------------------------------------------------------------------- +// Return the frequency of the processor in Hz. +// + +double PME::GetCPUClockSpeedFast(void) +{ + int64 i64_perf_start, i64_perf_freq, i64_perf_end; + int64 i64_clock_start,i64_clock_end; + double d_loop_period, d_clock_freq; + + //----------------------------------------------------------------------- + // Query the performance of the Windows high resolution timer. + //----------------------------------------------------------------------- + QueryPerformanceFrequency((LARGE_INTEGER*)&i64_perf_freq); + + //----------------------------------------------------------------------- + // Query the current value of the Windows high resolution timer. + //----------------------------------------------------------------------- + QueryPerformanceCounter((LARGE_INTEGER*)&i64_perf_start); + i64_perf_end = 0; + + //----------------------------------------------------------------------- + // Time of loop of 250000 windows cycles with RDTSC + //----------------------------------------------------------------------- + RDTSC(i64_clock_start); + while(i64_perf_end<i64_perf_start+250000) + { + QueryPerformanceCounter((LARGE_INTEGER*)&i64_perf_end); + } + RDTSC(i64_clock_end); + + //----------------------------------------------------------------------- + // Caclulate the frequency of the RDTSC timer and therefore calculate + // the frequency of the processor. + //----------------------------------------------------------------------- + i64_clock_end -= i64_clock_start; + + d_loop_period = ((double)(i64_perf_freq)) / 250000.0; + d_clock_freq = ((double)(i64_clock_end & 0xffffffff))*d_loop_period; + + return (float)d_clock_freq; +} + + + +// takes 1 second +double PME::GetCPUClockSpeedSlow(void) +{ + + if (m_CPUClockSpeed != 0) + return m_CPUClockSpeed; + + unsigned long start_ms, stop_ms; + unsigned long start_tsc,stop_tsc; + + // boosting priority helps with noise. its optional and i dont think + // it helps all that much + + PME * pme = PME::Instance(); + + pme->SetProcessPriority(ProcessPriorityHigh); + + // wait for millisecond boundary + start_ms = GetTickCount() + 5; + while (start_ms <= GetTickCount()); + + // read timestamp (you could use QueryPerformanceCounter in hires mode if you want) +#ifdef COMPILER_MSVC64 + RDTSC(start_tsc); +#else + __asm + { + rdtsc + mov dword ptr [start_tsc+0],eax + mov dword ptr [start_tsc+4],edx + } +#endif + + // wait for end + stop_ms = start_ms + 1000; // longer wait gives better resolution + while (stop_ms > GetTickCount()); + + // read timestamp (you could use QueryPerformanceCounter in hires mode if you want) +#ifdef COMPILER_MSVC64 + RDTSC(stop_tsc); +#else + __asm + { + rdtsc + mov dword ptr [stop_tsc+0],eax + mov dword ptr [stop_tsc+4],edx + } +#endif + + + // normalize priority + pme->SetProcessPriority(ProcessPriorityNormal); + + // return clock speed + // optionally here you could round to known clocks, like speeds that are multimples + // of 100, 133, 166, etc. + m_CPUClockSpeed = ((stop_tsc - start_tsc) * 1000.0) / (double)(stop_ms - start_ms); + return m_CPUClockSpeed; + +} + + + +const unsigned short cccr_escr_map[NCOUNTERS][8] = +{ + { + 0x3B2, + 0x3B4, + 0x3AA, + 0x3B6, + 0x3AC, + 0x3C8, + 0x3A2, + 0x3A0, + }, + { + 0x3B2, + 0x3B4, + 0x3AA, + 0x3B6, + 0x3AC, + 0x3C8, + 0x3A2, + 0x3A0, + }, + { + 0x3B3, + 0x3B5, + 0x3AB, + 0x3B7, + 0x3AD, + 0x3C9, + 0x3A3, + 0x3A1, + }, + { + 0x3B3, + 0x3B5, + 0x3AB, + 0x3B7, + 0x3AD, + 0x3C9, + 0x3A3, + 0x3A1, + }, + { + + 0x3C0, + 0x3C4, + 0x3C2, + }, + { + 0x3C0, + 0x3C4, + 0x3C2, + }, + { + 0x3C1, + 0x3C5, + 0x3C3, + }, + { + 0x3C1, + 0x3C5, + 0x3C3, + }, + { + 0x3A6, + 0x3A4, + 0x3AE, + 0x3B0, + 0, + 0x3A8, + }, + { + 0x3A6, + 0x3A4, + 0x3AE, + 0x3B0, + 0, + 0x3A8, + }, + { + + 0x3A7, + 0x3A5, + 0x3AF, + 0x3B1, + 0, + 0x3A9, + }, + { + + 0x3A7, + 0x3A5, + 0x3AF, + 0x3B1, + 0, + 0x3A9, + }, + { + + 0x3BA, + 0x3CA, + 0x3BC, + 0x3BE, + 0x3B8, + 0x3CC, + 0x3E0, + }, + { + + 0x3BA, + 0x3CA, + 0x3BC, + 0x3BE, + 0x3B8, + 0x3CC, + 0x3E0, + }, + { + + 0x3BB, + 0x3CB, + 0x3BD, + 0, + 0x3B9, + 0x3CD, + 0x3E1, + }, + { + + + 0x3BB, + 0x3CB, + 0x3BD, + 0, + 0x3B9, + 0x3CD, + 0x3E1, + }, + { + 0x3BA, + 0x3CA, + 0x3BC, + 0x3BE, + 0x3B8, + 0x3CC, + 0x3E0, + }, + { + + 0x3BB, + 0x3CB, + 0x3BD, + 0, + 0x3B9, + 0x3CD, + 0x3E1, + }, +}; + +#ifdef DBGFLAG_VALIDATE +//----------------------------------------------------------------------------- +// Purpose: Ensure that all of our internal structures are consistent, and +// account for all memory that we've allocated. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void PME::Validate( CValidator &validator, tchar *pchName ) +{ + validator.Push( _T("PME"), this, pchName ); + + validator.ClaimMemory( this ); + + validator.ClaimMemory( cache ); + + validator.ClaimMemory( ( void * ) vendor_name.c_str( ) ); + validator.ClaimMemory( ( void * ) brand.c_str( ) ); + + validator.Pop( ); +} +#endif // DBGFLAG_VALIDATE + +#pragma warning( default : 4530 ) // warning: exception handler -GX option +#endif + diff --git a/external/vpc/tier0/resource.h b/external/vpc/tier0/resource.h new file mode 100644 index 0000000..6a4ef65 --- /dev/null +++ b/external/vpc/tier0/resource.h @@ -0,0 +1,35 @@ +//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by assert_dialog.rc +// +#define IDD_ASSERT_DIALOG 101 +#define IDC_FILENAME_CONTROL 1000 +#define IDC_LINE_CONTROL 1001 +#define IDC_IGNORE_FILE 1002 +#define IDC_IGNORE_NEARBY 1003 +#define IDC_IGNORE_NUMLINES 1004 +#define IDC_IGNORE_THIS 1005 +#define IDC_BREAK 1006 +#define IDC_IGNORE_ALL 1008 +#define IDC_IGNORE_ALWAYS 1009 +#define IDC_IGNORE_NUMTIMES 1010 +#define IDC_ASSERT_MSG_CTRL 1011 +#define IDC_NOID -1 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 103 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1005 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/external/vpc/tier0/stacktools.cpp b/external/vpc/tier0/stacktools.cpp new file mode 100644 index 0000000..7a93d61 --- /dev/null +++ b/external/vpc/tier0/stacktools.cpp @@ -0,0 +1,1682 @@ +//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//===========================================================================// + +#include "pch_tier0.h" +#include "tier0/stacktools.h" +#include "tier0/threadtools.h" +#include "tier0/icommandline.h" + +#include "tier0/valve_off.h" + +#if defined( PLATFORM_WINDOWS_PC ) +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <dbghelp.h> +#endif + +#if defined( PLATFORM_X360 ) +#include <xbdm.h> +#include "xbox/xbox_console.h" +#include "xbox/xbox_vxconsole.h" +#include <map> +#include <set> +#endif + +#include "tier0/valve_on.h" + +#include "tier0/memdbgon.h" + + +#if !defined( ENABLE_RUNTIME_STACK_TRANSLATION ) //disable the whole toolset + +int GetCallStack( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) +{ + return 0; +} + +int GetCallStack_Fast( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) +{ + return 0; +} + +//where we'll find our PDB's for win32. Translation will not work until this has been called once (even if with NULL) +void SetStackTranslationSymbolSearchPath( const char *szSemicolonSeparatedList ) +{ +} + +void StackToolsNotify_LoadedLibrary( const char *szLibName ) +{ +} + +int TranslateStackInfo( const void * const *pCallStack, int iCallStackCount, tchar *szOutput, int iOutBufferSize, const tchar *szEntrySeparator, TranslateStackInfo_StyleFlags_t style ) +{ + if( iOutBufferSize > 0 ) + *szOutput = '\0'; + + return 0; +} + +void PreloadStackInformation( const void **pAddresses, int iAddressCount ) +{ +} + +bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) +{ + if( iMaxFileNameLength > 0 ) + *pFileNameOut = '\0'; + + return false; +} + +bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) +{ + if( iMaxSymbolNameLength > 0 ) + *pSymbolNameOut = '\0'; + + return false; +} + +bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) +{ + if( iMaxModuleNameLength > 0 ) + *pModuleNameOut = '\0'; + + return false; +} + +#else //#if !defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + +//=============================================================================================================== +// Shared Windows/X360 code +//=============================================================================================================== + +CTHREADLOCALPTR( CStackTop_Base ) g_StackTop; + +class CStackTop_FriendFuncs : public CStackTop_Base +{ +public: + friend int AppendParentStackTrace( void **pReturnAddressesOut, int iArrayCount, int iAlreadyFilled ); + friend int GetCallStack_Fast( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ); +}; + + +inline int AppendParentStackTrace( void **pReturnAddressesOut, int iArrayCount, int iAlreadyFilled ) +{ + CStackTop_FriendFuncs *pTop = (CStackTop_FriendFuncs *)(CStackTop_Base *)g_StackTop; + if( pTop != NULL ) + { + if( pTop->m_pReplaceAddress != NULL ) + { + for( int i = iAlreadyFilled; --i >= 0; ) + { + if( pReturnAddressesOut[i] == pTop->m_pReplaceAddress ) + { + iAlreadyFilled = i; + break; + } + } + } + + if( pTop->m_iParentStackTraceLength != 0 ) + { + int iCopy = MIN( iArrayCount - iAlreadyFilled, pTop->m_iParentStackTraceLength ); + memcpy( pReturnAddressesOut + iAlreadyFilled, pTop->m_pParentStackTrace, iCopy * sizeof( void * ) ); + iAlreadyFilled += iCopy; + } + } + + return iAlreadyFilled; +} + +inline bool ValidStackAddress( void *pAddress, const void *pNoLessThan, const void *pNoGreaterThan ) +{ + if( (uint64)pAddress & 3 ) + return false; + if( pAddress < pNoLessThan ) //frame pointer traversal should always increase the pointer + return false; + if( pAddress > pNoGreaterThan ) //never traverse outside the stack (Oh 0xCCCCCCCC, how I hate you) + return false; + +#if defined( WIN32 ) && !defined( _X360 ) && 1 + if( IsBadReadPtr( pAddress, (sizeof( void * ) * 2) ) ) //safety net, but also throws an exception (handled internally) to stop bad access + return false; +#endif + + return true; +} + +#pragma auto_inline( off ) +int GetCallStack_Fast( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) +{ + //Only tested in windows. This function won't work with frame pointer omission enabled. "vpc /nofpo" all projects +#if (defined( TIER0_FPO_DISABLED ) || defined( _DEBUG )) &&\ + (defined( WIN32 ) && !defined( _X360 )) + void *pStackCrawlEBP; + __asm + { + mov [pStackCrawlEBP], ebp; + } + + /* + With frame pointer omission disabled, this should be the pattern all the way up the stack + [ebp+00] Old ebp value + [ebp+04] Return address + */ + + void *pNoLessThan = pStackCrawlEBP; //impossible for a valid stack to traverse before this address + int i; + + CStackTop_FriendFuncs *pTop = (CStackTop_FriendFuncs *)(CStackTop_Base *)g_StackTop; + if( pTop != NULL ) //we can do fewer error checks if we have a valid reference point for the top of the stack + { + void *pNoGreaterThan = pTop->m_pStackBase; + + //skips + for( i = 0; i != iSkipCount; ++i ) + { + if( (pStackCrawlEBP < pNoLessThan) || (pStackCrawlEBP > pNoGreaterThan) ) + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, 0 ); + + pNoLessThan = pStackCrawlEBP; + pStackCrawlEBP = *(void **)pStackCrawlEBP; //should be pointing at old ebp value + } + + //store + for( i = 0; i != iArrayCount; ++i ) + { + if( (pStackCrawlEBP < pNoLessThan) || (pStackCrawlEBP > pNoGreaterThan) ) + break; + + pReturnAddressesOut[i] = *((void **)pStackCrawlEBP + 1); + + pNoLessThan = pStackCrawlEBP; + pStackCrawlEBP = *(void **)pStackCrawlEBP; //should be pointing at old ebp value + } + + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, i ); + } + else + { + void *pNoGreaterThan = ((unsigned char *)pNoLessThan) + (1024 * 1024); //standard stack is 1MB. TODO: Get actual stack end address if available since this check isn't foolproof + + //skips + for( i = 0; i != iSkipCount; ++i ) + { + if( !ValidStackAddress( pStackCrawlEBP, pNoLessThan, pNoGreaterThan ) ) + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, 0 ); + + pNoLessThan = pStackCrawlEBP; + pStackCrawlEBP = *(void **)pStackCrawlEBP; //should be pointing at old ebp value + } + + //store + for( i = 0; i != iArrayCount; ++i ) + { + if( !ValidStackAddress( pStackCrawlEBP, pNoLessThan, pNoGreaterThan ) ) + break; + + pReturnAddressesOut[i] = *((void **)pStackCrawlEBP + 1); + + pNoLessThan = pStackCrawlEBP; + pStackCrawlEBP = *(void **)pStackCrawlEBP; //should be pointing at old ebp value + } + + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, i ); + } +#endif + + return 0; +} +#pragma auto_inline( on ) + + + + + + +#if defined( WIN32 ) && !defined( _X360 ) +//=============================================================================================================== +// Windows version of the toolset +//=============================================================================================================== + + +#if defined( TIER0_FPO_DISABLED ) +//# define USE_CAPTURESTACKBACKTRACE //faster than StackWalk64, but only works on XP or newer and only with Frame Pointer Omission optimization disabled(/Oy-) for every function it traces through +#endif + + +#if defined(_M_IX86) || defined(_M_X64) +# define USE_STACKWALK64 +# if defined(_M_IX86) +# define STACKWALK64_MACHINETYPE IMAGE_FILE_MACHINE_I386 +# else +# define STACKWALK64_MACHINETYPE IMAGE_FILE_MACHINE_AMD64 +# endif +#endif + +typedef DWORD (WINAPI *PFN_SymGetOptions)( VOID ); +typedef DWORD (WINAPI *PFN_SymSetOptions)( IN DWORD SymOptions ); +typedef BOOL (WINAPI *PFN_SymSetSearchPath)( IN HANDLE hProcess, IN PSTR SearchPath ); +typedef BOOL (WINAPI *PFN_SymInitialize)( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess ); +typedef BOOL (WINAPI *PFN_SymCleanup)( IN HANDLE hProcess ); +typedef BOOL (WINAPI *PFN_SymEnumerateModules64)( IN HANDLE hProcess, IN PSYM_ENUMMODULES_CALLBACK64 EnumModulesCallback, IN PVOID UserContext ); +typedef BOOL (WINAPI *PFN_EnumerateLoadedModules64)( IN HANDLE hProcess, IN PENUMLOADED_MODULES_CALLBACK64 EnumLoadedModulesCallback, IN PVOID UserContext ); +typedef DWORD64 (WINAPI *PFN_SymLoadModule64)( IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll ); +typedef BOOL (WINAPI *PFN_SymUnloadModule64)( IN HANDLE hProcess, IN DWORD64 BaseOfDll ); +typedef BOOL (WINAPI *PFN_SymFromAddr)( IN HANDLE hProcess, IN DWORD64 Address, OUT PDWORD64 Displacement, IN OUT PSYMBOL_INFO Symbol ); +typedef BOOL (WINAPI *PFN_SymGetLineFromAddr64)( IN HANDLE hProcess, IN DWORD64 qwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line64 ); +typedef BOOL (WINAPI *PFN_SymGetModuleInfo64)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PIMAGEHLP_MODULE64 ModuleInfo ); +typedef BOOL (WINAPI *PFN_StackWalk64)( DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress ); +typedef USHORT (WINAPI *PFN_CaptureStackBackTrace)( IN ULONG FramesToSkip, IN ULONG FramesToCapture, OUT PVOID *BackTrace, OUT OPTIONAL PULONG BackTraceHash ); + + +DWORD WINAPI SymGetOptions_DummyFn( VOID ) +{ + return 0; +} + +DWORD WINAPI SymSetOptions_DummyFn( IN DWORD SymOptions ) +{ + return 0; +} + +BOOL WINAPI SymSetSearchPath_DummyFn( IN HANDLE hProcess, IN PSTR SearchPath ) +{ + return FALSE; +} + +BOOL WINAPI SymInitialize_DummyFn( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess ) +{ + return FALSE; +} + +BOOL WINAPI SymCleanup_DummyFn( IN HANDLE hProcess ) +{ + return TRUE; +} + +BOOL WINAPI SymEnumerateModules64_DummyFn( IN HANDLE hProcess, IN PSYM_ENUMMODULES_CALLBACK64 EnumModulesCallback, IN PVOID UserContext ) +{ + return FALSE; +} + +BOOL WINAPI EnumerateLoadedModules64_DummyFn( IN HANDLE hProcess, IN PENUMLOADED_MODULES_CALLBACK64 EnumLoadedModulesCallback, IN PVOID UserContext ) +{ + return FALSE; +} + +DWORD64 WINAPI SymLoadModule64_DummyFn( IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll ) +{ + return 0; +} + +BOOL WINAPI SymUnloadModule64_DummyFn( IN HANDLE hProcess, IN DWORD64 BaseOfDll ) +{ + return FALSE; +} + +BOOL WINAPI SymFromAddr_DummyFn( IN HANDLE hProcess, IN DWORD64 Address, OUT PDWORD64 Displacement, IN OUT PSYMBOL_INFO Symbol ) +{ + return FALSE; +} + +BOOL WINAPI SymGetLineFromAddr64_DummyFn( IN HANDLE hProcess, IN DWORD64 qwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line64 ) +{ + return FALSE; +} + +BOOL WINAPI SymGetModuleInfo64_DummyFn( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PIMAGEHLP_MODULE64 ModuleInfo ) +{ + return FALSE; +} + + + +BOOL WINAPI StackWalk64_DummyFn( DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress ) +{ + return FALSE; +} + +USHORT WINAPI CaptureStackBackTrace_DummyFn( IN ULONG FramesToSkip, IN ULONG FramesToCapture, OUT PVOID *BackTrace, OUT OPTIONAL PULONG BackTraceHash ) +{ + return 0; +} + +class CHelperFunctionsLoader +{ +public: + CHelperFunctionsLoader( void ) + { + m_bIsInitialized = false; + m_bShouldReloadSymbols = false; + m_hDbgHelpDll = NULL; + m_szPDBSearchPath = NULL; + m_pSymInitialize = SymInitialize_DummyFn; + m_pSymCleanup = SymCleanup_DummyFn; + m_pSymSetOptions = SymSetOptions_DummyFn; + m_pSymGetOptions = SymGetOptions_DummyFn; + m_pSymSetSearchPath = SymSetSearchPath_DummyFn; + m_pSymEnumerateModules64 = SymEnumerateModules64_DummyFn; + m_pEnumerateLoadedModules64 = EnumerateLoadedModules64_DummyFn; + m_pSymLoadModule64 = SymLoadModule64_DummyFn; + m_pSymUnloadModule64 = SymUnloadModule64_DummyFn; + m_pSymFromAddr = SymFromAddr_DummyFn; + m_pSymGetLineFromAddr64 = SymGetLineFromAddr64_DummyFn; + m_pSymGetModuleInfo64 = SymGetModuleInfo64_DummyFn; + +#if defined( USE_STACKWALK64 ) + m_pStackWalk64 = StackWalk64_DummyFn; +#endif + +#if defined( USE_CAPTURESTACKBACKTRACE ) + m_pCaptureStackBackTrace = CaptureStackBackTrace_DummyFn; + m_hNTDllDll = NULL; +#endif + } + + ~CHelperFunctionsLoader( void ) + { + m_pSymCleanup( m_hProcess ); + + if( m_hDbgHelpDll != NULL ) + ::FreeLibrary( m_hDbgHelpDll ); + +#if defined( USE_CAPTURESTACKBACKTRACE ) + if( m_hNTDllDll != NULL ) + ::FreeLibrary( m_hNTDllDll ); +#endif + + if( m_szPDBSearchPath != NULL ) + delete []m_szPDBSearchPath; + } + + static BOOL CALLBACK UnloadSymbolsCallback( PSTR ModuleName, DWORD64 BaseOfDll, PVOID UserContext ) + { + const CHelperFunctionsLoader *pThis = ((CHelperFunctionsLoader *)UserContext); + pThis->m_pSymUnloadModule64( pThis->m_hProcess, BaseOfDll ); + return TRUE; + } + +#if _MSC_VER >= 1600 + static BOOL CALLBACK LoadSymbolsCallback( PCSTR ModuleName, DWORD64 ModuleBase, ULONG ModuleSize, PVOID UserContext ) +#else + static BOOL CALLBACK LoadSymbolsCallback( PSTR ModuleName, DWORD64 ModuleBase, ULONG ModuleSize, PVOID UserContext ) +#endif + { + const CHelperFunctionsLoader *pThis = ((CHelperFunctionsLoader *)UserContext); + //SymLoadModule64( IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll ); + pThis->m_pSymLoadModule64( pThis->m_hProcess, NULL, (PSTR)ModuleName, (PSTR)ModuleName, ModuleBase, ModuleSize ); + return TRUE; + } + + void TryLoadingNewSymbols( void ) + { + AUTO_LOCK_FM( m_Mutex ); + + if( m_bIsInitialized ) + { + //m_pSymEnumerateModules64( m_hProcess, UnloadSymbolsCallback, this ); //unloaded modules we've already loaded + m_pEnumerateLoadedModules64( m_hProcess, LoadSymbolsCallback, this ); //load everything + m_bShouldReloadSymbols = false; + } + } + + void SetStackTranslationSymbolSearchPath( const char *szSemicolonSeparatedList ) + { + AUTO_LOCK_FM( m_Mutex ); + + if( m_szPDBSearchPath != NULL ) + delete []m_szPDBSearchPath; + + if( szSemicolonSeparatedList == NULL ) + { + m_szPDBSearchPath = NULL; + return; + } + + int iLength = (int)strlen( szSemicolonSeparatedList ) + 1; + char *pNewPath = new char [iLength]; + memcpy( pNewPath, szSemicolonSeparatedList, iLength ); + m_szPDBSearchPath = pNewPath; + + //re-init search paths. Or if we haven't yet loaded dbghelp.dll, this will go to the dummy function and do nothing + m_pSymSetSearchPath( m_hProcess, m_szPDBSearchPath ); + //TryLoadingNewSymbols(); + m_bShouldReloadSymbols = true; + } + + bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) + { + if( pAddress == NULL ) + return false; + + AUTO_LOCK_FM( m_Mutex ); + + unsigned char genericbuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME*sizeof(TCHAR)]; + + ((PSYMBOL_INFO)genericbuffer)->SizeOfStruct = sizeof(SYMBOL_INFO); + ((PSYMBOL_INFO)genericbuffer)->MaxNameLen = MAX_SYM_NAME; + + DWORD64 dwDisplacement; + if( m_pSymFromAddr( m_hProcess, (DWORD64)pAddress, &dwDisplacement, (PSYMBOL_INFO)genericbuffer) ) + { + strncpy( pSymbolNameOut, ((PSYMBOL_INFO)genericbuffer)->Name, iMaxSymbolNameLength ); + if( pDisplacementOut != NULL ) + *pDisplacementOut = dwDisplacement; + return true; + } + + return false; + } + + bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) + { + if( pAddress == NULL ) + return false; + + AUTO_LOCK_FM( m_Mutex ); + + tchar szBuffer[1024]; + szBuffer[0] = _T('\0'); + + IMAGEHLP_LINE64 imageHelpLine64; + imageHelpLine64.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + imageHelpLine64.FileName = szBuffer; + + DWORD dwDisplacement; + if( m_pSymGetLineFromAddr64( m_hProcess, (DWORD64)pAddress, &dwDisplacement, &imageHelpLine64 ) ) + { + strncpy( pFileNameOut, imageHelpLine64.FileName, iMaxFileNameLength ); + iLineNumberOut = imageHelpLine64.LineNumber; + + if( pDisplacementOut != NULL ) + *pDisplacementOut = dwDisplacement; + + return true; + } + + return false; + } + + bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) + { + AUTO_LOCK_FM( m_Mutex ); + IMAGEHLP_MODULE64 moduleInfo; + + moduleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE64); + + if ( m_pSymGetModuleInfo64( m_hProcess, (DWORD64)pAddress, &moduleInfo ) ) + { + strncpy( pModuleNameOut, moduleInfo.ModuleName, iMaxModuleNameLength ); + return true; + } + + return false; + } + + //only returns false if we ran out of buffer space. + bool TranslatePointer( const void * const pAddress, tchar *pTranslationOut, int iTranslationBufferLength, TranslateStackInfo_StyleFlags_t style ) + { + //AUTO_LOCK( m_Mutex ); + + if( pTranslationOut == NULL ) + return false; + + if( iTranslationBufferLength <= 0 ) + return false; + + //sample desired output + // valid translation - "tier0.dll!CHelperFunctionsLoader::TranslatePointer - u:\Dev\L4D\src\tier0\stacktools.cpp(162) + 4 bytes" + // fallback translation - "tier0.dll!0x01234567" + + tchar *pWrite = pTranslationOut; + *pWrite = '\0'; + int iLength; + + if( style & TSISTYLEFLAG_MODULENAME ) + { + if( !this->GetModuleNameFromAddress( pAddress, pWrite, iTranslationBufferLength ) ) + strncpy( pWrite, "unknown_module", iTranslationBufferLength ); + + iLength = (int)strlen( pWrite ); + pWrite += iLength; + iTranslationBufferLength -= iLength; + + if( iTranslationBufferLength < 2 ) + return false; //need more buffer + + if( style & TSISTYLEFLAG_SYMBOLNAME ) + { + *pWrite = '!'; + ++pWrite; + --iTranslationBufferLength; + *pWrite = '\0'; + } + } + + //use symbol name to test if the rest is going to work. So grab it whether they want it or not + if( !this->GetSymbolNameFromAddress( pAddress, pWrite, iTranslationBufferLength, NULL ) ) + { + int nBytesWritten = _snprintf( pWrite, iTranslationBufferLength, "0x%p", pAddress ); + if ( nBytesWritten < 0 ) + { + *pWrite = '\0'; // if we can't write all of the line/lineandoffset, don't write any at all + return false; + } + return true; + } + else if( style & TSISTYLEFLAG_SYMBOLNAME ) + { + iLength = (int)strlen( pWrite ); + pWrite += iLength; + iTranslationBufferLength -= iLength; + } + else + { + *pWrite = '\0'; //symbol name lookup worked, but unwanted, discard + } + + if( style & (TSISTYLEFLAG_FULLPATH | TSISTYLEFLAG_SHORTPATH | TSISTYLEFLAG_LINE | TSISTYLEFLAG_LINEANDOFFSET) ) + { + if( pWrite != pTranslationOut ) //if we've written anything yet, separate the printed data from the file name and line + { + if( iTranslationBufferLength < 6 ) + return false; //need more buffer + + pWrite[0] = ' '; //append " - " + pWrite[1] = '-'; + pWrite[2] = ' '; + pWrite[3] = '\0'; + pWrite += 3; + iTranslationBufferLength -= 3; + } + + uint32 iLine; + uint32 iDisplacement; + char szFileName[MAX_PATH]; + if( this->GetFileAndLineFromAddress( pAddress, szFileName, MAX_PATH, iLine, &iDisplacement ) ) + { + if( style & TSISTYLEFLAG_FULLPATH ) + { + iLength = (int)strlen( szFileName ); + if ( iTranslationBufferLength < iLength + 1 ) + return false; + + memcpy( pWrite, szFileName, iLength + 1 ); + pWrite += iLength; + iTranslationBufferLength -= iLength; + } + else if( style & TSISTYLEFLAG_SHORTPATH ) + { + //shorten the path and copy + iLength = (int)strlen( szFileName ); + char *pShortened = szFileName + iLength; + int iSlashesAllowed = 3; + while( pShortened > szFileName ) + { + if( (*pShortened == '\\') || (*pShortened == '/') ) + { + --iSlashesAllowed; + if( iSlashesAllowed == 0 ) + break; + } + + --pShortened; + } + + iLength = (int)strlen( pShortened ); + if( iTranslationBufferLength < iLength + 1 ) + { + //Remove the " - " that we can't append to + pWrite -= 3; + iTranslationBufferLength += 3; + *pWrite = '\0'; + return false; + } + + memcpy( pWrite, szFileName, iLength + 1 ); + pWrite += iLength; + iTranslationBufferLength -= iLength; + } + + if( style & (TSISTYLEFLAG_LINE | TSISTYLEFLAG_LINEANDOFFSET) ) + { + int nBytesWritten = _snprintf( pWrite, iTranslationBufferLength, ((style & TSISTYLEFLAG_LINEANDOFFSET) && (iDisplacement != 0)) ? "(%d) + %d bytes" : "(%d)", iLine, iDisplacement ); + if ( nBytesWritten < 0 ) + { + *pWrite = '\0'; // if we can't write all of the line/lineandoffset, don't write any at all + return false; + } + + pWrite += nBytesWritten; + iTranslationBufferLength -= nBytesWritten; + } + } + else + { + //Remove the " - " that we didn't append to + pWrite -= 3; + iTranslationBufferLength += 3; + *pWrite = '\0'; + } + } + + return true; + } + + + //about to actually use the functions, load if necessary + void EnsureReady( void ) + { + if( m_bIsInitialized ) + { + if( m_bShouldReloadSymbols ) + TryLoadingNewSymbols(); + + return; + } + + AUTO_LOCK_FM( m_Mutex ); + + //Only enabled for P4 and Steam Beta builds + if( (CommandLine()->FindParm( "-steam" ) != 0) && //is steam + (CommandLine()->FindParm( "-internalbuild" ) == 0) ) //is not steam beta + { + //disable the toolset by falsifying initialized state + m_bIsInitialized = true; + return; + } + + m_hProcess = GetCurrentProcess(); + if( m_hProcess == NULL ) + return; + + m_bIsInitialized = true; + + // get the function pointer directly so that we don't have to include the .lib, and that + // we can easily change it to using our own dll when this code is used on win98/ME/2K machines + m_hDbgHelpDll = ::LoadLibrary( "DbgHelp.dll" ); + if ( !m_hDbgHelpDll ) + { + //it's possible it's just way too early to initialize (as shown with attempts at using these tools in the memory allocator) + if( m_szPDBSearchPath == NULL ) //not a rock solid check, but pretty good compromise between endless failing initialization and general failure due to trying too early + m_bIsInitialized = false; + + return; + } + + m_pSymInitialize = (PFN_SymInitialize) ::GetProcAddress( m_hDbgHelpDll, "SymInitialize" ); + if( m_pSymInitialize == NULL ) + { + //very bad + ::FreeLibrary( m_hDbgHelpDll ); + m_hDbgHelpDll = NULL; + m_pSymInitialize = SymInitialize_DummyFn; + return; + } + + m_pSymCleanup = (PFN_SymCleanup) ::GetProcAddress( m_hDbgHelpDll, "SymCleanup" ); + if( m_pSymCleanup == NULL ) + m_pSymCleanup = SymCleanup_DummyFn; + + m_pSymGetOptions = (PFN_SymGetOptions) ::GetProcAddress( m_hDbgHelpDll, "SymGetOptions" ); + if( m_pSymGetOptions == NULL ) + m_pSymGetOptions = SymGetOptions_DummyFn; + + m_pSymSetOptions = (PFN_SymSetOptions) ::GetProcAddress( m_hDbgHelpDll, "SymSetOptions" ); + if( m_pSymSetOptions == NULL ) + m_pSymSetOptions = SymSetOptions_DummyFn; + + m_pSymSetSearchPath = (PFN_SymSetSearchPath) ::GetProcAddress( m_hDbgHelpDll, "SymSetSearchPath" ); + if( m_pSymSetSearchPath == NULL ) + m_pSymSetSearchPath = SymSetSearchPath_DummyFn; + + m_pSymEnumerateModules64 = (PFN_SymEnumerateModules64) ::GetProcAddress( m_hDbgHelpDll, "SymEnumerateModules64" ); + if( m_pSymEnumerateModules64 == NULL ) + m_pSymEnumerateModules64 = SymEnumerateModules64_DummyFn; + + m_pEnumerateLoadedModules64 = (PFN_EnumerateLoadedModules64) ::GetProcAddress( m_hDbgHelpDll, "EnumerateLoadedModules64" ); + if( m_pEnumerateLoadedModules64 == NULL ) + m_pEnumerateLoadedModules64 = EnumerateLoadedModules64_DummyFn; + + m_pSymLoadModule64 = (PFN_SymLoadModule64) ::GetProcAddress( m_hDbgHelpDll, "SymLoadModule64" ); + if( m_pSymLoadModule64 == NULL ) + m_pSymLoadModule64 = SymLoadModule64_DummyFn; + + m_pSymUnloadModule64 = (PFN_SymUnloadModule64) ::GetProcAddress( m_hDbgHelpDll, "SymUnloadModule64" ); + if( m_pSymUnloadModule64 == NULL ) + m_pSymUnloadModule64 = SymUnloadModule64_DummyFn; + + m_pSymFromAddr = (PFN_SymFromAddr) ::GetProcAddress( m_hDbgHelpDll, "SymFromAddr" ); + if( m_pSymFromAddr == NULL ) + m_pSymFromAddr = SymFromAddr_DummyFn; + + m_pSymGetLineFromAddr64 = (PFN_SymGetLineFromAddr64) ::GetProcAddress( m_hDbgHelpDll, "SymGetLineFromAddr64" ); + if( m_pSymGetLineFromAddr64 == NULL ) + m_pSymGetLineFromAddr64 = SymGetLineFromAddr64_DummyFn; + + m_pSymGetModuleInfo64 = (PFN_SymGetModuleInfo64) ::GetProcAddress( m_hDbgHelpDll, "SymGetModuleInfo64" ); + if( m_pSymGetModuleInfo64 == NULL ) + m_pSymGetModuleInfo64 = SymGetModuleInfo64_DummyFn; + +#if defined( USE_STACKWALK64 ) + m_pStackWalk64 = (PFN_StackWalk64) ::GetProcAddress( m_hDbgHelpDll, "StackWalk64" ); + if( m_pStackWalk64 == NULL ) + m_pStackWalk64 = StackWalk64_DummyFn; +#endif + + +#if defined( USE_CAPTURESTACKBACKTRACE ) + m_hNTDllDll = ::LoadLibrary( "ntdll.dll" ); + + m_pCaptureStackBackTrace = (PFN_CaptureStackBackTrace) ::GetProcAddress( m_hNTDllDll, "RtlCaptureStackBackTrace" ); + if( m_pCaptureStackBackTrace == NULL ) + m_pCaptureStackBackTrace = CaptureStackBackTrace_DummyFn; +#endif + + + m_pSymSetOptions( m_pSymGetOptions() | + SYMOPT_DEFERRED_LOADS | //load on demand + SYMOPT_EXACT_SYMBOLS | //don't load the wrong file + SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_NO_PROMPTS | //don't prompt ever + SYMOPT_LOAD_LINES ); //load line info + + m_pSymInitialize( m_hProcess, m_szPDBSearchPath, FALSE ); + TryLoadingNewSymbols(); + } + + bool m_bIsInitialized; + bool m_bShouldReloadSymbols; + HANDLE m_hProcess; + HMODULE m_hDbgHelpDll; + char *m_szPDBSearchPath; + CThreadFastMutex m_Mutex; //DbgHelp functions are all single threaded. + + PFN_SymInitialize m_pSymInitialize; + PFN_SymCleanup m_pSymCleanup; + PFN_SymGetOptions m_pSymGetOptions; + PFN_SymSetOptions m_pSymSetOptions; + PFN_SymSetSearchPath m_pSymSetSearchPath; + PFN_SymEnumerateModules64 m_pSymEnumerateModules64; + PFN_EnumerateLoadedModules64 m_pEnumerateLoadedModules64; + PFN_SymLoadModule64 m_pSymLoadModule64; + PFN_SymUnloadModule64 m_pSymUnloadModule64; + + PFN_SymFromAddr m_pSymFromAddr; + PFN_SymGetLineFromAddr64 m_pSymGetLineFromAddr64; + PFN_SymGetModuleInfo64 m_pSymGetModuleInfo64; + +#if defined( USE_STACKWALK64 ) + PFN_StackWalk64 m_pStackWalk64; +#endif + +#if defined( USE_CAPTURESTACKBACKTRACE ) + HMODULE m_hNTDllDll; + PFN_CaptureStackBackTrace m_pCaptureStackBackTrace; +#endif +}; +static CHelperFunctionsLoader s_HelperFunctions; + + + +#if defined( USE_STACKWALK64 ) //most reliable method thanks to boatloads of windows helper functions. Also the slowest. +int CrawlStack_StackWalk64( CONTEXT *pExceptionContext, void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) +{ + s_HelperFunctions.EnsureReady(); + + AUTO_LOCK_FM( s_HelperFunctions.m_Mutex ); + + CONTEXT currentContext; + memcpy( ¤tContext, pExceptionContext, sizeof( CONTEXT ) ); + + STACKFRAME64 sfFrame = { 0 }; //memset(&sfFrame, 0x0, sizeof(sfFrame)); + sfFrame.AddrPC.Mode = sfFrame.AddrFrame.Mode = AddrModeFlat; + sfFrame.AddrPC.Offset = currentContext.Eip; + sfFrame.AddrFrame.Offset = currentContext.Ebp; + + HANDLE hThread = GetCurrentThread(); + + int i; + for( i = 0; i != iSkipCount; ++i ) //skip entries that the requesting function thinks are uninformative + { + if(!s_HelperFunctions.m_pStackWalk64( STACKWALK64_MACHINETYPE, s_HelperFunctions.m_hProcess, hThread, &sfFrame, ¤tContext, NULL, NULL, NULL, NULL ) || + (sfFrame.AddrFrame.Offset == 0) ) + { + return 0; + } + } + + for( i = 0; i != iArrayCount; ++i ) + { + if(!s_HelperFunctions.m_pStackWalk64( STACKWALK64_MACHINETYPE, s_HelperFunctions.m_hProcess, hThread, &sfFrame, ¤tContext, NULL, NULL, NULL, NULL ) || + (sfFrame.AddrFrame.Offset == 0) ) + { + break; + } + pReturnAddressesOut[i] = (void *)sfFrame.AddrPC.Offset; + } + + return i; +} + +void GetCallStackReturnAddresses_Exception( void **CallStackReturnAddresses, int *pRetCount, int iSkipCount, _EXCEPTION_POINTERS * pExceptionInfo ) +{ + int iCount = CrawlStack_StackWalk64( pExceptionInfo->ContextRecord, CallStackReturnAddresses, *pRetCount, iSkipCount + 1 ); //skipping RaiseException() + *pRetCount = iCount; +} +#endif //#if defined( USE_STACKWALK64 ) + + + +int GetCallStack( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) +{ + s_HelperFunctions.EnsureReady(); + + ++iSkipCount; //skip this function + +#if defined( USE_CAPTURESTACKBACKTRACE ) + if( s_HelperFunctions.m_pCaptureStackBackTrace != CaptureStackBackTrace_DummyFn ) + { + //docs state a total limit of 63 back traces between skipped and stored + int iRetVal = s_HelperFunctions.m_pCaptureStackBackTrace( iSkipCount, MIN( iArrayCount, 63 - iSkipCount ), pReturnAddressesOut, NULL ); + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, iRetVal ); + } +#endif +#if defined( USE_STACKWALK64 ) + if( s_HelperFunctions.m_pStackWalk64 != StackWalk64_DummyFn ) + { + int iInOutArrayCount = iArrayCount; //array count becomes both input and output with exception handler version + __try + { + ::RaiseException( 0, EXCEPTION_NONCONTINUABLE, 0, NULL ); + } + __except ( GetCallStackReturnAddresses_Exception( pReturnAddressesOut, &iInOutArrayCount, iSkipCount, GetExceptionInformation() ), EXCEPTION_EXECUTE_HANDLER ) + { + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, iInOutArrayCount ); + } + } +#endif + + return GetCallStack_Fast( pReturnAddressesOut, iArrayCount, iSkipCount ); +} + +void SetStackTranslationSymbolSearchPath( const char *szSemicolonSeparatedList ) +{ + s_HelperFunctions.SetStackTranslationSymbolSearchPath( szSemicolonSeparatedList ); +} + +void StackToolsNotify_LoadedLibrary( const char *szLibName ) +{ + s_HelperFunctions.m_bShouldReloadSymbols = true; +} + +int TranslateStackInfo( const void * const *pCallStack, int iCallStackCount, tchar *szOutput, int iOutBufferSize, const tchar *szEntrySeparator, TranslateStackInfo_StyleFlags_t style ) +{ + s_HelperFunctions.EnsureReady(); + tchar *szStartOutput = szOutput; + + if( szEntrySeparator == NULL ) + szEntrySeparator = _T(""); + + int iSeparatorSize = (int)strlen( szEntrySeparator ); + + for( int i = 0; i < iCallStackCount; ++i ) + { + if( !s_HelperFunctions.TranslatePointer( pCallStack[i], szOutput, iOutBufferSize, style ) ) + { + return i; + } + + int iLength = (int)strlen( szOutput ); + szOutput += iLength; + iOutBufferSize -= iLength; + + if( iOutBufferSize > iSeparatorSize ) + { + memcpy( szOutput, szEntrySeparator, iSeparatorSize * sizeof( tchar ) ); + szOutput += iSeparatorSize; + iOutBufferSize -= iSeparatorSize; + } + *szOutput = '\0'; + } + + szOutput -= iSeparatorSize; + if( szOutput >= szStartOutput ) + *szOutput = '\0'; + + return iCallStackCount; +} + +void PreloadStackInformation( void * const *pAddresses, int iAddressCount ) +{ + //nop on anything but 360 +} + +bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) +{ + s_HelperFunctions.EnsureReady(); + return s_HelperFunctions.GetFileAndLineFromAddress( pAddress, pFileNameOut, iMaxFileNameLength, iLineNumberOut, pDisplacementOut ); +} + +bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) +{ + s_HelperFunctions.EnsureReady(); + return s_HelperFunctions.GetSymbolNameFromAddress( pAddress, pSymbolNameOut, iMaxSymbolNameLength, pDisplacementOut ); +} + +bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) +{ + s_HelperFunctions.EnsureReady(); + return s_HelperFunctions.GetModuleNameFromAddress( pAddress, pModuleNameOut, iMaxModuleNameLength ); +} + +#else //#if defined( WIN32 ) && !defined( _X360 ) + +//=============================================================================================================== +// X360 version of the toolset +//=============================================================================================================== + +class C360StackTranslationHelper +{ +public: + C360StackTranslationHelper( void ) + { + m_bInitialized = true; + } + + ~C360StackTranslationHelper( void ) + { + StringSet_t::const_iterator iter; + + //module names + { + iter = m_ModuleNameSet.begin(); + while(iter != m_ModuleNameSet.end()) + { + char *pModuleName = (char*)(*iter); + delete []pModuleName; + iter++; + } + m_ModuleNameSet.clear(); + } + + //file names + { + iter = m_FileNameSet.begin(); + while(iter != m_FileNameSet.end()) + { + char *pFileName = (char*)(*iter); + delete []pFileName; + iter++; + } + m_FileNameSet.clear(); + } + + //symbol names + { + iter = m_SymbolNameSet.begin(); + while(iter != m_SymbolNameSet.end()) + { + char *pSymbolName = (char*)(*iter); + delete []pSymbolName; + iter++; + } + m_SymbolNameSet.clear(); + } + + m_bInitialized = false; + } + +private: + struct StackAddressInfo_t; +public: + + inline StackAddressInfo_t *CreateEntry( const FullStackInfo_t &info ) + { + std::pair<AddressInfoMapIter_t, bool> retval = m_AddressInfoMap.insert( AddressInfoMapEntry_t( info.pAddress, StackAddressInfo_t() ) ); + if( retval.first->second.szModule != NULL ) + return &retval.first->second; //already initialized + + retval.first->second.iLine = info.iLine; + + + //share strings + + //module + { + const char *pModuleName; + StringSet_t::const_iterator iter = m_ModuleNameSet.find( info.szModuleName ); + if ( iter == m_ModuleNameSet.end() ) + { + int nLen = strlen(info.szModuleName) + 1; + pModuleName = new char [nLen]; + memcpy( (char *)pModuleName, info.szModuleName, nLen ); + m_ModuleNameSet.insert( pModuleName ); + } + else + { + pModuleName = (char *)(*iter); + } + + retval.first->second.szModule = pModuleName; + } + + //file + { + const char *pFileName; + StringSet_t::const_iterator iter = m_FileNameSet.find( info.szFileName ); + if ( iter == m_FileNameSet.end() ) + { + int nLen = strlen(info.szFileName) + 1; + pFileName = new char [nLen]; + memcpy( (char *)pFileName, info.szFileName, nLen ); + m_FileNameSet.insert( pFileName ); + } + else + { + pFileName = (char *)(*iter); + } + + retval.first->second.szFileName = pFileName; + } + + //symbol + { + const char *pSymbolName; + StringSet_t::const_iterator iter = m_SymbolNameSet.find( info.szSymbol ); + if ( iter == m_SymbolNameSet.end() ) + { + int nLen = strlen(info.szSymbol) + 1; + pSymbolName = new char [nLen]; + memcpy( (char *)pSymbolName, info.szSymbol, nLen ); + m_SymbolNameSet.insert( pSymbolName ); + } + else + { + pSymbolName = (char *)(*iter); + } + + retval.first->second.szSymbol = pSymbolName; + } + + return &retval.first->second; + } + + inline StackAddressInfo_t *FindInfoEntry( const void *pAddress ) + { + AddressInfoMapIter_t Iter = m_AddressInfoMap.find( pAddress ); + if( Iter != m_AddressInfoMap.end() ) + return &Iter->second; + + return NULL; + } + + inline int RetrieveStackInfo( const void * const *pAddresses, FullStackInfo_t *pReturnedStructs, int iAddressCount ) + { + int ReturnedTranslatedCount = -1; + //construct the message + // Header Finished Count(out) Input Count Input Array Returned data write address + int iMessageSize = 2 + sizeof( int * ) + sizeof( uint32 ) + (sizeof( void * ) * iAddressCount) + sizeof( FullStackInfo_t * ); + uint8 *pMessage = (uint8 *)stackalloc( iMessageSize ); + uint8 *pMessageWrite = pMessage; + pMessageWrite[0] = XBX_DBG_BNH_STACKTRANSLATOR; //have this message handled by stack translator handler + pMessageWrite[1] = ST_BHC_GETTRANSLATIONINFO; + pMessageWrite += 2; + *(int **)pMessageWrite = (int *)BigDWord( (DWORD)&ReturnedTranslatedCount ); + pMessageWrite += sizeof( int * ); + *(uint32 *)pMessageWrite = (uint32)BigDWord( (DWORD)iAddressCount ); + pMessageWrite += sizeof( uint32 ); + memcpy( pMessageWrite, pAddresses, iAddressCount * sizeof( void * ) ); + pMessageWrite += iAddressCount * sizeof( void * ); + *(FullStackInfo_t **)pMessageWrite = (FullStackInfo_t *)BigDWord( (DWORD)pReturnedStructs ); + bool bSuccess = XBX_SendBinaryData( pMessage, iMessageSize, false, 30000 ); + ReturnedTranslatedCount = BigDWord( ReturnedTranslatedCount ); + + if( bSuccess && (ReturnedTranslatedCount > 0) ) + { + return ReturnedTranslatedCount; + } + return 0; + } + + inline StackAddressInfo_t *CreateEntry( const void *pAddress ) + { + //ask VXConsole for information about the addresses we're clueless about + + FullStackInfo_t ReturnedData; + ReturnedData.pAddress = pAddress; + ReturnedData.szFileName[0] = '\0'; //strncpy( ReturnedData.szFileName, "FileUninitialized", sizeof( ReturnedData.szFileName ) ); + ReturnedData.szModuleName[0] = '\0'; //strncpy( ReturnedData.szModuleName, "ModuleUninitialized", sizeof( ReturnedData.szModuleName ) ); + ReturnedData.szSymbol[0] = '\0'; //strncpy( ReturnedData.szSymbol, "SymbolUninitialized", sizeof( ReturnedData.szSymbol ) ); + ReturnedData.iLine = 0; + ReturnedData.iSymbolOffset = 0; + + int iTranslated = RetrieveStackInfo( &pAddress, &ReturnedData, 1 ); + + if( iTranslated == 1 ) + { + //store + return CreateEntry( ReturnedData ); + } + + return FindInfoEntry( pAddress ); //probably won't work, but last ditch. + } + + inline StackAddressInfo_t *FindOrCreateEntry( const void *pAddress ) + { + StackAddressInfo_t *pReturn = FindInfoEntry( pAddress ); + if( pReturn == NULL ) + { + pReturn = CreateEntry( pAddress ); + } + + return pReturn; + } + + inline void LoadStackInformation( void * const *pAddresses, int iAddressCount ) + { + Assert( (iAddressCount > 0) && (pAddresses != NULL) ); + + int iNeedLoading = 0; + void **pNeedLoading = (void **)stackalloc( sizeof( const void * ) * iAddressCount ); //addresses we need to ask VXConsole about + + for( int i = 0; i != iAddressCount; ++i ) + { + if( FindInfoEntry( pAddresses[i] ) == NULL ) + { + //need to load this address + pNeedLoading[iNeedLoading] = pAddresses[i]; + ++iNeedLoading; + } + } + + if( iNeedLoading != 0 ) + { + //ask VXConsole for information about the addresses we're clueless about + FullStackInfo_t *pReturnedStructs = (FullStackInfo_t *)stackalloc( sizeof( FullStackInfo_t ) * iNeedLoading ); + + for( int i = 0; i < iNeedLoading; ++i ) + { + pReturnedStructs[i].pAddress = 0; + pReturnedStructs[i].szFileName[0] = '\0'; //strncpy( pReturnedStructs[i].szFileName, "FileUninitialized", sizeof( pReturnedStructs[i].szFileName ) ); + pReturnedStructs[i].szModuleName[0] = '\0'; //strncpy( pReturnedStructs[i].szModuleName, "ModuleUninitialized", sizeof( pReturnedStructs[i].szModuleName ) ); + pReturnedStructs[i].szSymbol[0] = '\0'; //strncpy( pReturnedStructs[i].szSymbol, "SymbolUninitialized", sizeof( pReturnedStructs[i].szSymbol ) ); + pReturnedStructs[i].iLine = 0; + pReturnedStructs[i].iSymbolOffset = 0; + } + + int iTranslated = RetrieveStackInfo( pNeedLoading, pReturnedStructs, iNeedLoading ); + + if( iTranslated == iNeedLoading ) + { + //store + for( int i = 0; i < iTranslated; ++i ) + { + CreateEntry( pReturnedStructs[i] ); + } + } + } + } + + inline bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) + { + StackAddressInfo_t *pInfo = FindOrCreateEntry( pAddress ); + if( pInfo && (pInfo->szFileName[0] != '\0') ) + { + strncpy( pFileNameOut, pInfo->szFileName, iMaxFileNameLength ); + iLineNumberOut = pInfo->iLine; + + if( pDisplacementOut ) + *pDisplacementOut = 0; //can't get line displacement on 360 + + return true; + } + + return false; + } + + inline bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) + { + StackAddressInfo_t *pInfo = FindOrCreateEntry( pAddress ); + if( pInfo && (pInfo->szSymbol[0] != '\0') ) + { + strncpy( pSymbolNameOut, pInfo->szSymbol, iMaxSymbolNameLength ); + + if( pDisplacementOut ) + *pDisplacementOut = pInfo->iSymbolOffset; + + return true; + } + + return false; + } + + inline bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) + { + StackAddressInfo_t *pInfo = FindOrCreateEntry( pAddress ); + if( pInfo && (pInfo->szModule[0] != '\0') ) + { + strncpy( pModuleNameOut, pInfo->szModule, iMaxModuleNameLength ); + return true; + } + + return false; + } + + + CThreadFastMutex m_hMutex; + +private: + +#pragma pack(push) +#pragma pack(1) + struct StackAddressInfo_t + { + StackAddressInfo_t( void ) : szModule(NULL), szFileName(NULL), szSymbol(NULL), iLine(0), iSymbolOffset(0) {} + const char *szModule; + const char *szFileName; + const char *szSymbol; + uint32 iLine; + uint32 iSymbolOffset; + }; +#pragma pack(pop) + + typedef std::map< const void *, StackAddressInfo_t, std::less<const void *>> AddressInfoMap_t; + typedef AddressInfoMap_t::iterator AddressInfoMapIter_t; + typedef AddressInfoMap_t::value_type AddressInfoMapEntry_t; + + class CStringLess + { + public: + bool operator()(const char *pszLeft, const char *pszRight ) const + { + return ( V_tier0_stricmp( pszLeft, pszRight ) < 0 ); + } + }; + + typedef std::set<const char *, CStringLess> StringSet_t; + + AddressInfoMap_t m_AddressInfoMap; //TODO: retire old entries? + StringSet_t m_ModuleNameSet; + StringSet_t m_FileNameSet; + StringSet_t m_SymbolNameSet; + bool m_bInitialized; + +}; +static C360StackTranslationHelper s_360StackTranslator; + + +int GetCallStack( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) +{ + ++iSkipCount; //skip this function + + //DmCaptureStackBackTrace() has no skip functionality, so we need to grab everything and skip within that list + void **pAllResults = (void **)stackalloc( sizeof( void * ) * (iArrayCount + iSkipCount) ); + if( DmCaptureStackBackTrace( iArrayCount + iSkipCount, pAllResults ) == XBDM_NOERR ) + { + for( int i = 0; i != iSkipCount; ++i ) + { + if( *pAllResults == NULL ) //DmCaptureStackBackTrace() NULL terminates the list instead of telling us how many were returned + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, 0 ); + + ++pAllResults; //move the pointer forward so the second loop indices match up + } + + for( int i = 0; i != iArrayCount; ++i ) + { + if( pAllResults[i] == NULL ) //DmCaptureStackBackTrace() NULL terminates the list instead of telling us how many were returned + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, i ); + + pReturnAddressesOut[i] = pAllResults[i]; + } + + return iArrayCount; //no room to append parent + } + + return GetCallStack_Fast( pReturnAddressesOut, iArrayCount, iSkipCount ); +} + + +void SetStackTranslationSymbolSearchPath( const char *szSemicolonSeparatedList ) +{ + //nop on 360 +} + + +void StackToolsNotify_LoadedLibrary( const char *szLibName ) +{ + //send off the notice to VXConsole + uint8 message[2]; + message[0] = XBX_DBG_BNH_STACKTRANSLATOR; //have this message handled by stack translator handler + message[1] = ST_BHC_LOADEDLIBARY; //loaded a library notification + XBX_SendBinaryData( message, 2 ); +} + + +int TranslateStackInfo( const void * const *pCallStack, int iCallStackCount, tchar *szOutput, int iOutBufferSize, const tchar *szEntrySeparator, TranslateStackInfo_StyleFlags_t style ) +{ + if( iCallStackCount == 0 ) + { + if( iOutBufferSize > 1 ) + *szOutput = '\0'; + + return 0; + } + + if( szEntrySeparator == NULL ) + szEntrySeparator = ""; + + int iSeparatorLength = strlen( szEntrySeparator ) + 1; + int iDataSize = (sizeof( void * ) * iCallStackCount) + (iSeparatorLength * sizeof( tchar )) + 1; //1 for style flags + + //360 is incapable of translation on it's own. Encode the stack for translation in VXConsole + //Encoded section is as such ":CSDECODE[encoded binary]" + int iEncodedSize = -EncodeBinaryToString( NULL, iDataSize, NULL, 0 ); //get needed buffer size + const tchar cControlPrefix[] = XBX_CALLSTACKDECODEPREFIX; + const size_t cControlLength = (sizeof( cControlPrefix )/sizeof(tchar)) - 1; //-1 to remove null terminator + + if( iOutBufferSize > (iEncodedSize + (int)cControlLength + 2) ) //+2 for ']' and null term + { + COMPILE_TIME_ASSERT( TSISTYLEFLAG_LAST < (1<<8) ); //need to update the encoder/decoder to use more than a byte for style flags + + uint8 *pData = (uint8 *)stackalloc( iDataSize ); + pData[0] = (uint8)style; + memcpy( pData + 1, szEntrySeparator, iSeparatorLength * sizeof( tchar ) ); + memcpy( pData + 1 + (iSeparatorLength * sizeof( tchar )), pCallStack, iCallStackCount * sizeof( void * ) ); + + memcpy( szOutput, XBX_CALLSTACKDECODEPREFIX, cControlLength * sizeof( tchar ) ); + int iLength = cControlLength + EncodeBinaryToString( pData, iDataSize, &szOutput[cControlLength], (iOutBufferSize - cControlLength) ); + + szOutput[iLength] = ']'; + szOutput[iLength + 1] = '\0'; + } + + return iCallStackCount; +} + +void PreloadStackInformation( void * const *pAddresses, int iAddressCount ) +{ + AUTO_LOCK_FM( s_360StackTranslator.m_hMutex ); + s_360StackTranslator.LoadStackInformation( pAddresses, iAddressCount ); +} + +bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) +{ + AUTO_LOCK_FM( s_360StackTranslator.m_hMutex ); + return s_360StackTranslator.GetFileAndLineFromAddress( pAddress, pFileNameOut, iMaxFileNameLength, iLineNumberOut, pDisplacementOut ); +} + +bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) +{ + AUTO_LOCK_FM( s_360StackTranslator.m_hMutex ); + return s_360StackTranslator.GetSymbolNameFromAddress( pAddress, pSymbolNameOut, iMaxSymbolNameLength, pDisplacementOut ); +} + +bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) +{ + AUTO_LOCK_FM( s_360StackTranslator.m_hMutex ); + return s_360StackTranslator.GetModuleNameFromAddress( pAddress, pModuleNameOut, iMaxModuleNameLength ); +} + +#endif //#else //#if defined( WIN32 ) && !defined( _X360 ) + +#endif //#if !defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + + + + +CCallStackStorage::CCallStackStorage( FN_GetCallStack GetStackFunction, uint32 iSkipCalls ) +{ + iValidEntries = GetStackFunction( pStack, ARRAYSIZE( pStack ), iSkipCalls + 1 ); +} + + + +CStackTop_CopyParentStack::CStackTop_CopyParentStack( void * const *pParentStackTrace, int iParentStackTraceLength ) +{ +#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + //miniature version of GetCallStack_Fast() +#if (defined( TIER0_FPO_DISABLED ) || defined( _DEBUG )) &&\ + (defined( WIN32 ) && !defined( _X360 )) + void *pStackCrawlEBP; + __asm + { + mov [pStackCrawlEBP], ebp; + } + pStackCrawlEBP = *(void **)pStackCrawlEBP; + m_pReplaceAddress = *((void **)pStackCrawlEBP + 1); + m_pStackBase = (void *)((void **)pStackCrawlEBP + 1); +#else + m_pReplaceAddress = NULL; + m_pStackBase = this; +#endif + + m_pParentStackTrace = NULL; + + if( (pParentStackTrace != NULL) && (iParentStackTraceLength > 0) ) + { + while( (iParentStackTraceLength > 0) && (pParentStackTrace[iParentStackTraceLength - 1] == NULL) ) + { + --iParentStackTraceLength; + } + + if( iParentStackTraceLength > 0 ) + { + m_pParentStackTrace = new void * [iParentStackTraceLength]; + memcpy( (void **)m_pParentStackTrace, pParentStackTrace, sizeof( void * ) * iParentStackTraceLength ); + } + } + + m_iParentStackTraceLength = iParentStackTraceLength; + + m_pPrevTop = g_StackTop; + g_StackTop = this; + Assert( (CStackTop_Base *)g_StackTop == this ); +#endif //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) +} + +CStackTop_CopyParentStack::~CStackTop_CopyParentStack( void ) +{ +#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + Assert( (CStackTop_Base *)g_StackTop == this ); + g_StackTop = m_pPrevTop; + + if( m_pParentStackTrace != NULL ) + { + delete []m_pParentStackTrace; + } +#endif +} + + + +CStackTop_ReferenceParentStack::CStackTop_ReferenceParentStack( void * const *pParentStackTrace, int iParentStackTraceLength ) +{ +#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + //miniature version of GetCallStack_Fast() +#if (defined( TIER0_FPO_DISABLED ) || defined( _DEBUG )) &&\ + (defined( WIN32 ) && !defined( _X360 )) + void *pStackCrawlEBP; + __asm + { + mov [pStackCrawlEBP], ebp; + } + pStackCrawlEBP = *(void **)pStackCrawlEBP; + m_pReplaceAddress = *((void **)pStackCrawlEBP + 1); + m_pStackBase = (void *)((void **)pStackCrawlEBP + 1); +#else + m_pReplaceAddress = NULL; + m_pStackBase = this; +#endif + + m_pParentStackTrace = pParentStackTrace; + + if( (pParentStackTrace != NULL) && (iParentStackTraceLength > 0) ) + { + while( (iParentStackTraceLength > 0) && (pParentStackTrace[iParentStackTraceLength - 1] == NULL) ) + { + --iParentStackTraceLength; + } + } + + m_iParentStackTraceLength = iParentStackTraceLength; + + m_pPrevTop = g_StackTop; + g_StackTop = this; + Assert( (CStackTop_Base *)g_StackTop == this ); +#endif //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) +} + +CStackTop_ReferenceParentStack::~CStackTop_ReferenceParentStack( void ) +{ +#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + Assert( (CStackTop_Base *)g_StackTop == this ); + g_StackTop = m_pPrevTop; + + ReleaseParentStackReferences(); +#endif +} + +void CStackTop_ReferenceParentStack::ReleaseParentStackReferences( void ) +{ +#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + m_pParentStackTrace = NULL; + m_iParentStackTraceLength = 0; +#endif +} + + + + +//Encodes data so that every byte's most significant bit is a 1. Ensuring no null terminators. +//This puts the encoded data in the 128-255 value range. Leaving all standard ascii characters for control. +//Returns string length (not including the written null terminator as is standard). +//Or if the buffer is too small. Returns negative of necessary buffer size (including room needed for null terminator) +int EncodeBinaryToString( const void *pToEncode, int iDataLength, char *pEncodeOut, int iEncodeBufferSize ) +{ + int iEncodedSize = iDataLength; + iEncodedSize += (iEncodedSize + 6) / 7; //Have 1 control byte for every 7 actual bytes + iEncodedSize += sizeof( uint32 ) + 1; //data size at the beginning of the blob and null terminator at the end + + if( (iEncodedSize > iEncodeBufferSize) || (pEncodeOut == NULL) || (pToEncode == NULL) ) + return -iEncodedSize; //not enough room + + uint8 *pEncodeWrite = (uint8 *)pEncodeOut; + + //first encode the data size. Encodes lowest 28 bits and discards the high 4 + pEncodeWrite[0] = ((iDataLength >> 21) & 0xFF) | 0x80; + pEncodeWrite[1] = ((iDataLength >> 14) & 0xFF) | 0x80; + pEncodeWrite[2] = ((iDataLength >> 7) & 0xFF) | 0x80; + pEncodeWrite[3] = ((iDataLength >> 0) & 0xFF) | 0x80; + pEncodeWrite += 4; + + const uint8 *pEncodeRead = (const uint8 *)pToEncode; + const uint8 *pEncodeStop = pEncodeRead + iDataLength; + uint8 *pEncodeWriteLastControlByte = pEncodeWrite; + int iControl = 0; + + //Encode the data + while( pEncodeRead < pEncodeStop ) + { + if( iControl == 0 ) + { + pEncodeWriteLastControlByte = pEncodeWrite; + *pEncodeWriteLastControlByte = 0x80; + } + else + { + *pEncodeWrite = *pEncodeRead | 0x80; //encoded data always has the MSB bit set (cheap avoidance of null terminators) + *pEncodeWriteLastControlByte |= (((*pEncodeRead) & 0x80) ^ 0x80) >> iControl; //We use the control byte to XOR the MSB back to original values on decode + ++pEncodeRead; + } + + ++pEncodeWrite; + ++iControl; + iControl &= 7; //8->0 + } + *pEncodeWrite = '\0'; + + return iEncodedSize - 1; +} + +//Decodes a string produced by EncodeBinaryToString(). Safe to decode in place if you don't mind trashing your string, binary byte count always less than string byte count. +//Returns: +// >= 0 is the decoded data size +// INT_MIN (most negative value possible) indicates an improperly formatted string (not our data) +// all other negative values are the negative of how much dest buffer size is necessary. +int DecodeBinaryFromString( const char *pString, void *pDestBuffer, int iDestBufferSize, char **ppParseFinishOut ) +{ + const uint8 *pDecodeRead = (const uint8 *)pString; + + if( (pDecodeRead[0] < 0x80) || (pDecodeRead[1] < 0x80) || (pDecodeRead[2] < 0x80) || (pDecodeRead[3] < 0x80) ) + { + if( ppParseFinishOut != NULL ) + *ppParseFinishOut = (char *)pString; + + return INT_MIN; //Don't know what the string is, but it's not our format + } + + int iDecodedSize = 0; + iDecodedSize |= (pDecodeRead[0] & 0x7F) << 21; + iDecodedSize |= (pDecodeRead[1] & 0x7F) << 14; + iDecodedSize |= (pDecodeRead[2] & 0x7F) << 7; + iDecodedSize |= (pDecodeRead[3] & 0x7F) << 0; + pDecodeRead += 4; + + int iTextLength = iDecodedSize; + iTextLength += (iTextLength + 6) / 7; //Have 1 control byte for every 7 actual bytes + + //make sure it's formatted properly + for( int i = 0; i != iTextLength; ++i ) + { + if( pDecodeRead[i] < 0x80 ) //encoded data always has MSB set + { + if( ppParseFinishOut != NULL ) + *ppParseFinishOut = (char *)pString; + + return INT_MIN; //either not our data, or part of the string is missing + } + } + + if( iDestBufferSize < iDecodedSize ) + { + if( ppParseFinishOut != NULL ) + *ppParseFinishOut = (char *)pDecodeRead; + + return -iDecodedSize; //dest buffer not big enough to hold the data + } + + const uint8 *pStopDecoding = pDecodeRead + iTextLength; + uint8 *pDecodeWrite = (uint8 *)pDestBuffer; + int iControl = 0; + int iLSBXOR = 0; + + while( pDecodeRead < pStopDecoding ) + { + if( iControl == 0 ) + { + iLSBXOR = *pDecodeRead; + } + else + { + *pDecodeWrite = *pDecodeRead ^ ((iLSBXOR << iControl) & 0x80); + ++pDecodeWrite; + } + + ++pDecodeRead; + ++iControl; + iControl &= 7; //8->0 + } + + if( ppParseFinishOut != NULL ) + *ppParseFinishOut = (char *)pDecodeRead; + + return iDecodedSize; +} + + diff --git a/external/vpc/tier0/threadtools.cpp b/external/vpc/tier0/threadtools.cpp new file mode 100644 index 0000000..735f233 --- /dev/null +++ b/external/vpc/tier0/threadtools.cpp @@ -0,0 +1,3096 @@ +//========== Copyright 2005, Valve Corporation, All rights reserved. ======== +// +// Purpose: +// +//============================================================================= + +#include "tier0/platform.h" + +#if defined( PLATFORM_WINDOWS_PC ) +#define WIN32_LEAN_AND_MEAN +#define _WIN32_WINNT 0x0403 +#include <windows.h> +#endif + +#ifdef PLATFORM_WINDOWS + #include <process.h> + #ifdef PLATFORM_WINDOWS_PC + #include <Mmsystem.h> + #pragma comment(lib, "winmm.lib") + #endif +#elif PLATFORM_PS3 + #include <sched.h> + #include <unistd.h> + #include <exception> + #include <errno.h> + #include <pthread.h> + #include <sys/time.h> + #include <sys/timer.h> + #define GetLastError() errno + typedef void *LPVOID; +#elif PLATFORM_POSIX + #include <sched.h> + #include <exception> + #include <errno.h> + #include <signal.h> + #include <pthread.h> + #include <sys/time.h> + #define GetLastError() errno + typedef void *LPVOID; +#if !defined(OSX) + #include <sys/fcntl.h> + #include <sys/unistd.h> + #define sem_unlink( arg ) +#else + #define pthread_yield pthread_yield_np + #include <mach/thread_act.h> +#endif // !OSX + +#endif + +#ifndef _PS3 +#include <memory.h> +#endif +#include "tier0/threadtools.h" + +#ifdef _X360 +#include "xbox/xbox_win32stubs.h" +#endif + +#include <map> + +// Must be last header... +#include "tier0/memdbgon.h" + +#ifdef _PS3 +#include "ps3/ps3_win32stubs.h" +#define NEW_WAIT_FOR_MULTIPLE_OBJECTS +bool gbCheckNotMultithreaded = true; +#endif + +#define THREADS_DEBUG 1 + +#define DEBUG_ERROR(XX) Assert(0) + +// Need to ensure initialized before other clients call in for main thread ID +#ifdef _WIN32 +#pragma warning(disable:4073) +#pragma init_seg(lib) +#endif + +#ifdef _WIN32 +ASSERT_INVARIANT(TT_SIZEOF_CRITICALSECTION == sizeof(CRITICAL_SECTION)); +ASSERT_INVARIANT(TT_INFINITE == INFINITE); +#endif + +// thread creation counter. +// this is used to provide a unique threadid for each running thread in g_nThreadID ( a thread local variable ). + +const int MAX_THREAD_IDS = 128; + +static volatile bool s_bThreadIDAllocated[MAX_THREAD_IDS]; + +#ifdef LINUX + +DLL_CLASS_EXPORT __thread int g_nThreadID; + +#elif defined(_PS3) + #include "tls_ps3.h" +#else + CTHREADLOCALINT g_nThreadID; +#endif + + +static CThreadFastMutex s_ThreadIDMutex; + +PLATFORM_INTERFACE void AllocateThreadID( void ) +{ + AUTO_LOCK( s_ThreadIDMutex ); + for( int i = 1; i < MAX_THREAD_IDS; i++ ) + { + if ( ! s_bThreadIDAllocated[i] ) + { + g_nThreadID = i; + s_bThreadIDAllocated[i] = true; + return; + } + } + Error( "Out of thread ids. Decrease the number of threads or increase MAX_THREAD_IDS\n" ); +} + +PLATFORM_INTERFACE void FreeThreadID( void ) +{ + AUTO_LOCK( s_ThreadIDMutex ); + int nThread = g_nThreadID; + if ( nThread ) + s_bThreadIDAllocated[nThread] = false; +} + + +//----------------------------------------------------------------------------- +// Simple thread functions. +// Because _beginthreadex uses stdcall, we need to convert to cdecl +//----------------------------------------------------------------------------- +struct ThreadProcInfo_t +{ + ThreadProcInfo_t( ThreadFunc_t pfnThread, void *pParam ) + : pfnThread( pfnThread), + pParam( pParam ) + { + } + + ThreadFunc_t pfnThread; + void * pParam; +}; + +//--------------------------------------------------------- + +#ifdef PLATFORM_WINDOWS +static DWORD WINAPI ThreadProcConvert( void *pParam ) +{ + ThreadProcInfo_t info = *((ThreadProcInfo_t *)pParam); + AllocateThreadID(); + delete ((ThreadProcInfo_t *)pParam); + unsigned nRet = (*info.pfnThread)(info.pParam); + FreeThreadID(); + return nRet; +} +#elif defined( PLATFORM_PS3 ) +union ThreadProcInfoUnion_t +{ + struct Val_t + { + ThreadFunc_t pfnThread; + void * pParam; + } + val; + uint64_t val64; +}; +static void ThreadProcConvertUnion( uint64_t param ) +{ + COMPILE_TIME_ASSERT( sizeof( ThreadProcInfoUnion_t ) == 8 ); + ThreadProcInfoUnion_t info; + info.val64 = param; + AllocateThreadID(); + unsigned nRet = (*info.val.pfnThread)(info.val.pParam); + FreeThreadID(); + sys_ppu_thread_exit( nRet ); +} +static void* ThreadProcConvert( void *pParam ) +{ + ThreadProcInfo_t info = *((ThreadProcInfo_t *)pParam); + AllocateThreadID(); + delete ((ThreadProcInfo_t *)pParam); + unsigned nRet = (*info.pfnThread)(info.pParam); + FreeThreadID(); + return ( void * ) nRet; +} + +#else +static void* ThreadProcConvert( void *pParam ) +{ + ThreadProcInfo_t info = *((ThreadProcInfo_t *)pParam); + AllocateThreadID(); + delete ((ThreadProcInfo_t *)pParam); + unsigned nRet = (*info.pfnThread)(info.pParam); + FreeThreadID(); + return ( void * ) nRet; +} +#endif + + + +#if defined( _PS3 ) + +/******************************************************************************* +* Thread Local Storage globals and functions +*******************************************************************************/ +#ifndef _PS3 +__thread void *gTLSValues[ MAX_TLS_VALUES ] = { NULL }; +__thread bool gTLSFlags[ MAX_TLS_VALUES ] = { false }; +__thread bool gbWaitObjectsCreated = false; +__thread sys_semaphore_t gWaitObjectsSemaphore; +#endif // !_PS3 + +static char gThreadName[28] = ""; + +// Simple TLS allocator. Linearly searches for a free slot. +uint32 TlsAlloc() +{ + for ( int i = 0; i < MAX_TLS_VALUES; ++i ) + { + if ( !gTLSFlags[i] ) + { + gTLSFlags[i] = true; + return i; + } + } + +#ifdef _PS3 + DEBUG_ERROR("TlsAlloc(): Out of TLS\n"); +#endif + + return 0xFFFFFFFF; +} + +void TlsFree( uint32 index ) +{ + gTLSValues[ index ] = NULL; + gTLSFlags[ index ] = false; +} + +void *TlsGetValue( uint32 index ) +{ + return gTLSValues[ index ]; +} + +void TlsSetValue( uint32 index, void *pValue ) +{ + gTLSValues[ index ] = pValue; +} +#endif //_PS3 + + + +#ifdef PLATFORM_WINDOWS +class CThreadHandleToIDMap +{ +public: + HANDLE m_hThread; + uint m_ThreadID; + CThreadHandleToIDMap *m_pNext; +}; +static CThreadHandleToIDMap *g_pThreadHandleToIDMaps = NULL; +static CThreadMutex g_ThreadHandleToIDMapMutex; +static volatile int g_nThreadHandleToIDMaps = 0; + +static void AddThreadHandleToIDMap( HANDLE hThread, uint threadID ) +{ + if ( !hThread ) + return; + + // Remember this handle/id combo. + CThreadHandleToIDMap *pMap = new CThreadHandleToIDMap; + pMap->m_hThread = hThread; + pMap->m_ThreadID = threadID; + + // Add it to the global list. + g_ThreadHandleToIDMapMutex.Lock(); + pMap->m_pNext = g_pThreadHandleToIDMaps; + g_pThreadHandleToIDMaps = pMap; + ++g_nThreadHandleToIDMaps; + + g_ThreadHandleToIDMapMutex.Unlock(); + + if ( g_nThreadHandleToIDMaps > 500 ) + Error( "ThreadHandleToIDMap overflow." ); +} + +// This assumes you've got g_ThreadHandleToIDMapMutex locked!! +static bool InternalLookupHandleToThreadIDMap( HANDLE hThread, CThreadHandleToIDMap* &pMap, CThreadHandleToIDMap** &ppPrev ) +{ + ppPrev = &g_pThreadHandleToIDMaps; + for ( pMap=g_pThreadHandleToIDMaps; pMap; pMap=pMap->m_pNext ) + { + if ( pMap->m_hThread == hThread ) + return true; + + ppPrev = &pMap->m_pNext; + } + + return false; +} + +static void RemoveThreadHandleToIDMap( HANDLE hThread ) +{ + if ( !hThread ) + return; + + CThreadHandleToIDMap *pMap, **ppPrev; + + g_ThreadHandleToIDMapMutex.Lock(); + + if ( g_nThreadHandleToIDMaps <= 0 ) + Error( "ThreadHandleToIDMap underflow." ); + + if ( InternalLookupHandleToThreadIDMap( hThread, pMap, ppPrev ) ) + { + *ppPrev = pMap->m_pNext; + delete pMap; + --g_nThreadHandleToIDMaps; + } + + g_ThreadHandleToIDMapMutex.Unlock(); +} + +static uint LookupThreadIDFromHandle( HANDLE hThread ) +{ + if ( hThread == NULL || hThread == GetCurrentThread() ) + return GetCurrentThreadId(); + + float flStartTime = Plat_FloatTime(); + while ( Plat_FloatTime() - flStartTime < 2 ) + { + CThreadHandleToIDMap *pMap, **ppPrev; + + g_ThreadHandleToIDMapMutex.Lock(); + bool bRet = InternalLookupHandleToThreadIDMap( hThread, pMap, ppPrev ); + g_ThreadHandleToIDMapMutex.Unlock(); + + if ( bRet ) + return pMap->m_ThreadID; + + // We should only get here if a thread that is just starting up is currently in AddThreadHandleToIDMap. + // Give up the timeslice and try again. + ThreadSleep( 1 ); + } + + Assert( !"LookupThreadIDFromHandle failed!" ); + Warning( "LookupThreadIDFromHandle couldn't find thread ID for handle." ); + return 0; +} +#endif + + +//--------------------------------------------------------- + +ThreadHandle_t * CreateTestThreads( ThreadFunc_t fnThread, int numThreads, int nProcessorsToDistribute ) +{ + ThreadHandle_t *pHandles = (new ThreadHandle_t[numThreads+1]) + 1; + pHandles[-1] = (ThreadHandle_t)INT_TO_POINTER( numThreads ); + for( int i = 0; i < numThreads; ++i ) + { + //TestThreads(); + ThreadHandle_t hThread; + const unsigned int nDefaultStackSize = 64 * 1024; // this stack size is used in case stackSize == 0 + hThread = CreateSimpleThread( fnThread, INT_TO_POINTER( i ), nDefaultStackSize ); + + if ( nProcessorsToDistribute ) + { + int32 mask = 1 << (i % nProcessorsToDistribute); + ThreadSetAffinity( hThread, mask ); + } + +/* + ThreadProcInfoUnion_t info; + info.val.pfnThread = fnThread; + info.val.pParam = (void*)(i); + if ( int nError = sys_ppu_thread_create( &hThread, ThreadProcConvertUnion, info.val64, 1001, nDefaultStackSize, SYS_PPU_THREAD_CREATE_JOINABLE, "SimpleThread" ) != CELL_OK ) + { + printf( "PROBLEM!\n" ); + Error( "Cannot create thread, error %d\n", nError ); + return 0; + } +*/ + //ThreadHandle_t hThread = CreateSimpleThread( fnThread, (void*)i ); + pHandles[i] = hThread; + } +// printf("Finishinged CreateTestThreads(%p,%d)\n", (void*)fnThread, numThreads ); + return pHandles; +} + +void JoinTestThreads( ThreadHandle_t *pHandles ) +{ + int nCount = POINTER_TO_INT( pHandles[-1] ); +// printf("Joining test threads @%p[%d]:\n", pHandles, nCount ); +// for( int i = 0; i < nCount; ++i ) +// { +// printf(" %p,\n", (void*)pHandles[i] ); +// } + for( int i = 0; i < nCount; ++i ) + { +// printf( "Joining %p", (void*) pHandles[i] ); +// if( !i ) sys_timer_usleep(100000); + ThreadJoin( pHandles[i] ); + ReleaseThreadHandle( pHandles[i] ); + } + delete[]( pHandles - 1 ); +} + + + +ThreadHandle_t CreateSimpleThread( ThreadFunc_t pfnThread, void *pParam, unsigned stackSize ) +{ +#ifdef PLATFORM_WINDOWS + DWORD threadID; + HANDLE hThread = (HANDLE)CreateThread( NULL, stackSize, ThreadProcConvert, new ThreadProcInfo_t( pfnThread, pParam ), 0, &threadID ); + AddThreadHandleToIDMap( hThread, threadID ); + return (ThreadHandle_t)hThread; +#elif PLATFORM_PS3 + //TestThreads(); + ThreadHandle_t th; + ThreadProcInfoUnion_t info; + info.val.pfnThread = pfnThread; + info.val.pParam = pParam; + const unsigned int nDefaultStackSize = 64 * 1024; // this stack size is used in case stackSize == 0 + if ( sys_ppu_thread_create( &th, ThreadProcConvertUnion, info.val64, 1001, stackSize ? stackSize : nDefaultStackSize, SYS_PPU_THREAD_CREATE_JOINABLE, "SimpleThread" ) != CELL_OK ) + { + AssertMsg1( 0, "Failed to create thread (error 0x%x)", errno ); + return 0; + } + return th; +#elif PLATFORM_POSIX + pthread_t tid; + pthread_create( &tid, NULL, ThreadProcConvert, new ThreadProcInfo_t( pfnThread, pParam ) ); + return ( ThreadHandle_t ) tid; +#else + Assert( 0 ); + DebuggerBreak(); + return 0; +#endif +} + + +bool ReleaseThreadHandle( ThreadHandle_t hThread ) +{ +#ifdef _WIN32 + bool bRetVal = ( CloseHandle( hThread ) != 0 ); + RemoveThreadHandleToIDMap( (HANDLE)hThread ); + return bRetVal; +#else + return true; +#endif +} + +//----------------------------------------------------------------------------- +// +// Wrappers for other simple threading operations +// +//----------------------------------------------------------------------------- + +void ThreadSleep(unsigned nMilliseconds) +{ +#ifdef _WIN32 + +#ifdef PLATFORM_WINDOWS_PC + static bool bInitialized = false; + if ( !bInitialized ) + { + bInitialized = true; + // Set the timer resolution to 1 ms (default is 10.0, 15.6, 2.5, 1.0 or + // some other value depending on hardware and software) so that we can + // use Sleep( 1 ) to avoid wasting CPU time without missing our frame + // rate. + timeBeginPeriod( 1 ); + } +#endif + + Sleep( nMilliseconds ); +#elif PLATFORM_PS3 + if( nMilliseconds == 0 ) + { + // sys_ppu_thread_yield doesn't seem to function properly, so sleep instead. + sys_timer_usleep( 60 ); + } + else + { + sys_timer_usleep( nMilliseconds * 1000 ); + } +#elif defined(POSIX) + usleep( nMilliseconds * 1000 ); +#endif +} + +//----------------------------------------------------------------------------- + +#ifndef ThreadGetCurrentId +ThreadId_t ThreadGetCurrentId() +{ +#ifdef _WIN32 + return GetCurrentThreadId(); +#elif defined( _PS3 ) + sys_ppu_thread_t th = 0; + sys_ppu_thread_get_id( &th ); + return th; +#elif defined(POSIX) + return (ThreadId_t)pthread_self(); +#else + Assert(0); + DebuggerBreak(); + return 0; +#endif +} +#endif + +//----------------------------------------------------------------------------- +ThreadHandle_t ThreadGetCurrentHandle() +{ +#ifdef _WIN32 + return (ThreadHandle_t)GetCurrentThread(); +#elif defined( _PS3 ) + sys_ppu_thread_t th = 0; + sys_ppu_thread_get_id( &th ); + return th; +#elif defined(POSIX) + return (ThreadHandle_t)pthread_self(); +#else + Assert(0); + DebuggerBreak(); + return 0; +#endif +} + +//----------------------------------------------------------------------------- + +int ThreadGetPriority( ThreadHandle_t hThread ) +{ + if ( !hThread ) + { + hThread = ThreadGetCurrentHandle(); + } + +#ifdef _WIN32 + return ::GetThreadPriority( (HANDLE)hThread ); +#elif defined( _PS3 ) + int iPri = 0; + sys_ppu_thread_get_priority( hThread, &iPri ); + return iPri; +#else + return 0; +#endif +} + +//----------------------------------------------------------------------------- + +bool ThreadSetPriority( ThreadHandle_t hThread, int priority ) +{ + if ( !hThread ) + { + hThread = ThreadGetCurrentHandle(); + } + +#ifdef _WIN32 + return ( SetThreadPriority(hThread, priority) != 0 ); +#elif defined( _PS3 ) + int retval = sys_ppu_thread_set_priority( hThread, priority ); + return retval >= CELL_OK; +#elif defined(POSIX) + struct sched_param thread_param; + thread_param.sched_priority = priority; + //pthread_setschedparam( (pthread_t ) hThread, SCHED_RR, &thread_param ); + return true; +#endif +} + +//----------------------------------------------------------------------------- + +void ThreadSetAffinity( ThreadHandle_t hThread, int nAffinityMask ) +{ + if ( !hThread ) + { + hThread = ThreadGetCurrentHandle(); + } + +#ifdef _WIN32 + SetThreadAffinityMask( hThread, nAffinityMask ); +#elif defined(POSIX) +// cpu_set_t cpuSet; +// CPU_ZERO( cpuSet ); +// for( int i = 0 ; i < 32; i++ ) +// if ( nAffinityMask & ( 1 << i ) ) +// CPU_SET( cpuSet, i ); +// sched_setaffinity( hThread, sizeof( cpuSet ), &cpuSet ); +#endif + +} + +//----------------------------------------------------------------------------- + +#ifndef _X360 +uint InitMainThread() +{ + ThreadSetDebugName( "MainThrd" ); + + return ThreadGetCurrentId(); +} + +unsigned long g_ThreadMainThreadID = InitMainThread(); + +bool ThreadInMainThread() +{ + return ( ThreadGetCurrentId() == g_ThreadMainThreadID ); +} + +void DeclareCurrentThreadIsMainThread() +{ + g_ThreadMainThreadID = ThreadGetCurrentId(); +} + +#else +byte *InitMainThread() +{ + byte b; + + return AlignValue( &b, 64*1024 ); +} +#define STACK_SIZE_360 327680 +byte *g_pBaseMainStack = InitMainThread(); +byte *g_pLimitMainStack = InitMainThread() - STACK_SIZE_360; +#endif + +//----------------------------------------------------------------------------- +bool ThreadJoin( ThreadHandle_t hThread, unsigned timeout ) +{ + if ( !hThread ) + { + return false; + } + +#ifdef _WIN32 + DWORD dwWait = WaitForSingleObject( (HANDLE)hThread, timeout ); + if ( dwWait == WAIT_TIMEOUT) + return false; + if ( dwWait != WAIT_OBJECT_0 && ( dwWait != WAIT_FAILED && GetLastError() != 0 ) ) + { + Assert( 0 ); + return false; + } +#elif defined( _PS3 ) + uint64 uiExitCode = 0; + int retval = sys_ppu_thread_join( hThread, &uiExitCode ); + return ( retval >= CELL_OK ); +#elif defined(POSIX) + if ( pthread_join( (pthread_t)hThread, NULL ) != 0 ) + return false; +#else + Assert(0); + DebuggerBreak(); +#endif + return true; +} + +//----------------------------------------------------------------------------- +void ThreadSetDebugName( ThreadHandle_t hThread, const char *pszName ) +{ +#ifdef WIN32 + if ( Plat_IsInDebugSession() ) + { +#define MS_VC_EXCEPTION 0x406d1388 + + typedef struct tagTHREADNAME_INFO + { + DWORD dwType; // must be 0x1000 + LPCSTR szName; // pointer to name (in same addr space) + DWORD dwThreadID; // thread ID (-1 caller thread) + DWORD dwFlags; // reserved for future use, most be zero + } THREADNAME_INFO; + + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = pszName; + info.dwThreadID = LookupThreadIDFromHandle( hThread ); + + if ( info.dwThreadID != 0 ) + { + info.dwFlags = 0; + + __try + { + RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(DWORD), (ULONG_PTR *)&info); + } + __except (EXCEPTION_CONTINUE_EXECUTION) + { + } + } + } +#endif +} + + + +//----------------------------------------------------------------------------- +// Used to thread LoadLibrary on the 360 +//----------------------------------------------------------------------------- +static ThreadedLoadLibraryFunc_t s_ThreadedLoadLibraryFunc = 0; +PLATFORM_INTERFACE void SetThreadedLoadLibraryFunc( ThreadedLoadLibraryFunc_t func ) +{ + s_ThreadedLoadLibraryFunc = func; +} + +PLATFORM_INTERFACE ThreadedLoadLibraryFunc_t GetThreadedLoadLibraryFunc() +{ + return s_ThreadedLoadLibraryFunc; +} + + +//----------------------------------------------------------------------------- +// +// CThreadSyncObject (note nothing uses this directly (I think) ) +// +//----------------------------------------------------------------------------- + + +#ifdef _PS3 +uint32_t CThreadSyncObject::m_bstaticMutexInitialized = false; +uint32_t CThreadSyncObject::m_bstaticMutexInitializing = false; +sys_lwmutex_t CThreadSyncObject::m_staticMutex; +#endif + + +CThreadSyncObject::CThreadSyncObject() +#ifdef _WIN32 + : m_hSyncObject( NULL ), m_bCreatedHandle(false) +#elif defined(POSIX) && !defined(PLATFORM_PS3) + : m_bInitalized( false ) +#endif +{ +#ifdef _PS3 + //Do we nee to initialise the staticMutex? + if (m_bstaticMutexInitialized) return; + + //If we are the first thread then create the mutex + if ( cellAtomicCompareAndSwap32(&m_bstaticMutexInitializing, false, true) == false ) + { + sys_lwmutex_attribute_t mutexAttr; + sys_lwmutex_attribute_initialize( mutexAttr ); + mutexAttr.attr_recursive = SYS_SYNC_RECURSIVE; + int err = sys_lwmutex_create( &m_staticMutex, &mutexAttr ); + Assert(err == CELL_OK); + m_bstaticMutexInitialized = true; + } + else + { + //Another thread is already in the process of initialising the mutex, wait for it + while ( !m_bstaticMutexInitialized ) + { + // sys_ppu_thread_yield doesn't seem to function properly, so sleep instead. + sys_timer_usleep( 60 ); + } + } +#endif +} + +//--------------------------------------------------------- + +CThreadSyncObject::~CThreadSyncObject() +{ +#ifdef _WIN32 + if ( m_hSyncObject && m_bCreatedHandle ) + { + if ( !CloseHandle(m_hSyncObject) ) + { + Assert( 0 ); + } + } +#elif defined(POSIX) && !defined( PLATFORM_PS3 ) + if ( m_bInitalized ) + { + pthread_cond_destroy( &m_Condition ); + pthread_mutex_destroy( &m_Mutex ); + m_bInitalized = false; + } +#endif +} + +//--------------------------------------------------------- + +bool CThreadSyncObject::operator!() const +{ +#if PLATFORM_PS3 + return m_bstaticMutexInitialized; +#elif defined( _WIN32 ) + return !m_hSyncObject; +#elif defined(POSIX) + return !m_bInitalized; +#endif +} + +//--------------------------------------------------------- + +void CThreadSyncObject::AssertUseable() +{ +#ifdef THREADS_DEBUG +#if PLATFORM_PS3 + AssertMsg( m_bstaticMutexInitialized, "Thread synchronization object is unuseable" ); +#elif defined( _WIN32 ) + AssertMsg( m_hSyncObject, "Thread synchronization object is unuseable" ); +#elif defined(POSIX) + AssertMsg( m_bInitalized, "Thread synchronization object is unuseable" ); +#endif +#endif +} + +//--------------------------------------------------------- + +#if defined(_WIN32) || ( defined(POSIX) && !defined( _PS3 ) ) +bool CThreadSyncObject::Wait( uint32 dwTimeout ) +{ +#ifdef THREADS_DEBUG + AssertUseable(); +#endif +#ifdef _WIN32 + return ( WaitForSingleObject( m_hSyncObject, dwTimeout ) == WAIT_OBJECT_0 ); +#elif defined( POSIX ) && !defined( PLATFORM_PS3 ) + pthread_mutex_lock( &m_Mutex ); + bool bRet = false; + if ( m_cSet > 0 ) + { + bRet = true; + m_bWakeForEvent = false; + } + else + { + volatile int ret = 0; + + while ( !m_bWakeForEvent && ret != ETIMEDOUT ) + { + struct timeval tv; + gettimeofday( &tv, NULL ); + volatile struct timespec tm; + + uint64 actualTimeout = dwTimeout; + + if ( dwTimeout == TT_INFINITE && m_bManualReset ) + actualTimeout = 10; // just wait 10 msec at most for manual reset events and loop instead + + volatile uint64 nNanoSec = (uint64)tv.tv_usec*1000 + (uint64)actualTimeout*1000000; + tm.tv_sec = tv.tv_sec + nNanoSec /1000000000; + tm.tv_nsec = nNanoSec % 1000000000; + + do + { + ret = pthread_cond_timedwait( &m_Condition, &m_Mutex, (const timespec *)&tm ); + } + while( ret == EINTR ); + + bRet = ( ret == 0 ); + + if ( m_bManualReset ) + { + if ( m_cSet ) + break; + if ( dwTimeout == TT_INFINITE && ret == ETIMEDOUT ) + ret = 0; // force the loop to spin back around + } + } + + if ( bRet ) + m_bWakeForEvent = false; + } + if ( !m_bManualReset && bRet ) + m_cSet = 0; + pthread_mutex_unlock( &m_Mutex ); + return bRet; +#endif +} +#endif + +#ifdef _WIN32 +#pragma optimize( "", off ) // The compiler optimizer screws up the function below. +#endif + +uint32 CThreadSyncObject::WaitForMultiple( int nObjects, CThreadSyncObject **ppObjects, bool bWaitAll, uint32 dwTimeout ) +{ +#if defined( _WIN32 ) + + CThreadSyncObject *pHandles = (CThreadSyncObject*)stackalloc( sizeof(CThreadSyncObject) * nObjects ); + for ( int i=0; i < nObjects; i++ ) + { + pHandles[i].m_hSyncObject = ppObjects[i]->m_hSyncObject; + } + + return WaitForMultiple( nObjects, pHandles, bWaitAll, dwTimeout ); + +#else + + // TODO: Need a more efficient implementation of this. + uint32 dwStartTime = 0; + + if ( dwTimeout != TT_INFINITE ) + dwStartTime = Plat_MSTime(); + + // If bWaitAll = true, then we need to track which ones were triggered. + char *pWasTriggered = NULL; + int nTriggered = 0; + if ( bWaitAll ) + { + pWasTriggered = (char*)stackalloc( nObjects ); + memset( pWasTriggered, 0, nObjects ); + } + + while ( 1 ) + { + for ( int i=0; i < nObjects; i++ ) + { + if ( bWaitAll && pWasTriggered[i] ) + continue; + +#ifdef _PS3 + Assert( !"Not implemented!" ); + if ( false ) +#else + if ( ppObjects[i]->Wait( 0 ) ) +#endif + { + ++nTriggered; + if ( bWaitAll ) + { + if ( nTriggered == nObjects ) + return 0; + else + pWasTriggered[i] = 1; + } + else + { + return i; + } + } + } + + // Timeout? + if ( dwTimeout != TT_INFINITE ) + { + if ( Plat_MSTime() - dwStartTime >= dwTimeout ) + return TW_TIMEOUT; + } + + ThreadSleep( 0 ); + } + +#endif +} + +#ifdef _WIN32 +#pragma optimize( "", on ) +#endif + +uint32 CThreadSyncObject::WaitForMultiple( int nObjects, CThreadSyncObject *pObjects, bool bWaitAll, uint32 dwTimeout ) +{ +#if defined(_WIN32 ) + + HANDLE *pHandles = (HANDLE*)stackalloc( sizeof(HANDLE) * nObjects ); + for ( int i=0; i < nObjects; i++ ) + { + pHandles[i] = pObjects[i].m_hSyncObject; + } + + DWORD ret = WaitForMultipleObjects( nObjects, pHandles, bWaitAll, dwTimeout ); + if ( ret == WAIT_TIMEOUT ) + return TW_TIMEOUT; + else if ( ret >= WAIT_OBJECT_0 && (ret-WAIT_OBJECT_0) < (uint32)nObjects ) + return (int)(ret - WAIT_OBJECT_0); + else if ( ret >= WAIT_ABANDONED_0 && (ret - WAIT_ABANDONED_0) < (uint32)nObjects ) + Error( "Unhandled WAIT_ABANDONED in WaitForMultipleObjects" ); + else if ( ret == WAIT_FAILED ) + return TW_FAILED; + else + Error( "Unknown return value (%lu) from WaitForMultipleObjects", ret ); + + // We'll never get here.. + return 0; + +#else + + CThreadSyncObject **ppObjects = (CThreadSyncObject**)stackalloc( sizeof( CThreadSyncObject* ) * nObjects ); + for ( int i=0; i < nObjects; i++ ) + { + ppObjects[i] = &pObjects[i]; + } + + return WaitForMultiple( nObjects, ppObjects, bWaitAll, dwTimeout ); + +#endif +} + +// To implement these, I need to check that casts are safe +uint32 CThreadEvent::WaitForMultiple( int nObjects, CThreadEvent *pObjects, bool bWaitAll, uint32 dwTimeout ) +{ + // If data ever gets added to CThreadEvent, then we need a different implementation. +#ifdef _PS3 + CThreadEvent **ppObjects = (CThreadEvent**)stackalloc( sizeof( CThreadEvent* ) * nObjects ); + for ( int i=0; i < nObjects; i++ ) + { + ppObjects[i] = &pObjects[i]; + } + return WaitForMultipleObjects( nObjects, ppObjects, bWaitAll, dwTimeout ); +#else + COMPILE_TIME_ASSERT( sizeof( CThreadSyncObject ) == 0 || sizeof( CThreadEvent ) == sizeof( CThreadSyncObject ) ); + return CThreadSyncObject::WaitForMultiple( nObjects, (CThreadSyncObject*)pObjects, bWaitAll, dwTimeout ); +#endif +} + + +uint32 CThreadEvent::WaitForMultiple( int nObjects, CThreadEvent **ppObjects, bool bWaitAll, uint32 dwTimeout ) +{ +#ifdef _PS3 + return WaitForMultipleObjects( nObjects, ppObjects, bWaitAll, dwTimeout ); +#else + // If data ever gets added to CThreadEvent, then we need a different implementation. + COMPILE_TIME_ASSERT( sizeof( CThreadSyncObject )== 0 || sizeof( CThreadEvent ) == sizeof( CThreadSyncObject ) ); + return CThreadSyncObject::WaitForMultiple( nObjects, (CThreadSyncObject**)ppObjects, bWaitAll, dwTimeout ); +#endif +} + + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +CThreadEvent::CThreadEvent( bool bManualReset ) +{ +#ifdef _WIN32 + m_hSyncObject = CreateEvent( NULL, bManualReset, FALSE, NULL ); + m_bCreatedHandle = true; + AssertMsg1(m_hSyncObject, "Failed to create event (error 0x%x)", GetLastError() ); +#elif defined( _PS3 ) + + m_bManualReset = bManualReset; + m_bSet = 0; + m_bInitalized = false; + m_numWaitingThread = 0; + + // set up linked list of wait objects + + memset(&m_waitObjects[0], 0, sizeof(m_waitObjects)); + m_pWaitObjectsList = &m_waitObjects[0]; + m_pWaitObjectsPool = &m_waitObjects[1]; + + for (int i = 2; i < CTHREADEVENT_MAX_WAITING_THREADS + 2; i++) + { + LLLinkNode(m_pWaitObjectsPool, &m_waitObjects[i]); + } +#elif defined( POSIX ) + pthread_mutexattr_t Attr; + pthread_mutexattr_init( &Attr ); + pthread_mutex_init( &m_Mutex, &Attr ); + pthread_mutexattr_destroy( &Attr ); + pthread_cond_init( &m_Condition, NULL ); + m_bInitalized = true; + m_cSet = 0; + m_bWakeForEvent = false; + m_bManualReset = bManualReset; +#else +#error "Implement me" +#endif +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +#ifdef _PS3 + +// +// linked list functionality +// + +//----------------------------------------------------------------------------- +// Purpose: Linked list implementation +//----------------------------------------------------------------------------- + +CThreadEventWaitObject* CThreadEvent::LLUnlinkNode(CThreadEventWaitObject *node) +{ + // <sergiy> Note: if you have a null-access crash here, it may mean that CTHREADEVENT_MAX_WAITING_THREADS is not high enough + // and the linked list pool is simply exhausted + node->m_pPrev->m_pNext = node->m_pNext; + if (node->m_pNext) node->m_pNext->m_pPrev = node->m_pPrev; + node->m_pNext = node->m_pPrev = NULL; + + return node; +} + +CThreadEventWaitObject* CThreadEvent::LLLinkNode(CThreadEventWaitObject* list, CThreadEventWaitObject *node) +{ + node->m_pNext = list->m_pNext; + if (node->m_pNext) + { + node->m_pNext->m_pPrev = node; + } + + list->m_pNext = node; + node->m_pPrev = list; + + return node; +} + +//----------------------------------------------------------------------------- +// Helper function to atomically write index into destination and set semaphore +// This is used by WaitForMultipleObjects(WAIT_ANY) because once the semaphore +// is set, the waiting thread also needs to know which event triggered it +// We do NOT need this to be atomic because if a number of events fire it doesn't +// matter which one of these we pick +//----------------------------------------------------------------------------- +void CThreadEventWaitObject::Set() +{ + *m_pFlag = m_index; + sys_semaphore_post(*m_pSemaphore, 1); +} + +// +// CThreadEvent::RegisterWaitingThread +// +void CThreadEvent::RegisterWaitingThread(sys_semaphore_t *pSemaphore, int index, int *flag) +{ + sys_lwmutex_lock(&m_staticMutex, 0); + + // if we are already set, then signal this semaphore + if (m_bSet) + { + CThreadEventWaitObject waitObject; + waitObject.Init(pSemaphore, index, flag); + waitObject.Set(); + + if (!m_bManualReset) + { + m_bSet = false; + } + } + else + { + if (!m_pWaitObjectsPool->m_pNext) + { + DEBUG_ERROR("CThreadEvent: Ran out of events; cannot register waiting thread\n"); + } + + // add this semaphore to linked list - can be added more than once it doesn't matter + + CThreadEventWaitObject *pWaitObject = LLUnlinkNode(m_pWaitObjectsPool->m_pNext); + + pWaitObject->Init(pSemaphore, index, flag); + + LLLinkNode(m_pWaitObjectsList, pWaitObject); + } + + sys_lwmutex_unlock(&m_staticMutex); +} + +// +// CThreadEvent::UnregisterWaitingThread +// +void CThreadEvent::UnregisterWaitingThread(sys_semaphore_t *pSemaphore) +{ + // remove all instances of this semaphore from linked list + + sys_lwmutex_lock(&m_staticMutex, 0); + + CThreadEventWaitObject *pWaitObject = m_pWaitObjectsList->m_pNext; + + while (pWaitObject) + { + CThreadEventWaitObject *pNext = pWaitObject->m_pNext; + + if (pWaitObject->m_pSemaphore == pSemaphore) + { + LLUnlinkNode(pWaitObject); + LLLinkNode(m_pWaitObjectsPool, pWaitObject); + } + + pWaitObject = pNext; + } + + sys_lwmutex_unlock(&m_staticMutex); +} + +#endif // _PS3 + + +#ifdef PLATFORM_WINDOWS + CThreadEvent::CThreadEvent( const char *name, bool initialState, bool bManualReset ) + { + m_hSyncObject = CreateEvent( NULL, bManualReset, (BOOL) initialState, name ); + AssertMsg1( m_hSyncObject, "Failed to create event (error 0x%x)", GetLastError() ); + } + + + NamedEventResult_t CThreadEvent::CheckNamedEvent( const char *name, uint32 dwTimeout ) + { + HANDLE eHandle = OpenEvent( SYNCHRONIZE, FALSE, name ); + + if ( eHandle == NULL ) return TT_EventDoesntExist; + + DWORD result = WaitForSingleObject( eHandle, dwTimeout ); + + return ( result == WAIT_OBJECT_0 ) ? TT_EventSignaled : TT_EventNotSignaled; + } +#endif + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + + +//--------------------------------------------------------- + +bool CThreadEvent::Set() +{ +////////////////////////////////////////////////////////////// +#ifndef NEW_WAIT_FOR_MULTIPLE_OBJECTS +////////////////////////////////////////////////////////////// + AssertUseable(); +#ifdef _WIN32 + return ( SetEvent( m_hSyncObject ) != 0 ); +#elif defined( _PS3 ) + + sys_lwmutex_lock(&m_staticMutex, 0); + + if (m_bManualReset) + { + //Mark event as set + m_bSet = true; + + //If any threads are already waiting then signal them to run + if (m_bInitalized) + { + int err = sys_semaphore_post( m_Semaphore, m_numWaitingThread); + Assert(err == CELL_OK); + } + } + else + { + //If any threads are already waiting then signal ONE to run, else signal next to run + + if (m_numWaitingThread>0) + { + int err = sys_semaphore_post( m_Semaphore, 1); + Assert(err == CELL_OK); + } + else + { + m_bSet=true; + } + } + + sys_lwmutex_unlock(&m_staticMutex); + + return true; + + +#elif defined(POSIX) + pthread_mutex_lock( &m_Mutex ); + m_cSet = 1; + m_bWakeForEvent = true; + int ret = pthread_cond_signal( &m_Condition ); + pthread_mutex_unlock( &m_Mutex ); + return ret == 0; +#endif + + +////////////////////////////////////////////////////////////// +#else // NEW_WAIT_FOR_MULTIPLE_OBJECTS +////////////////////////////////////////////////////////////// + + sys_lwmutex_lock(&m_staticMutex, 0); + + //Mark event as set + m_bSet = true; + + // signal registered semaphores + while (m_pWaitObjectsList->m_pNext) + { + CThreadEventWaitObject *pWaitObject = LLUnlinkNode(m_pWaitObjectsList->m_pNext); + + pWaitObject->Set(); + + LLLinkNode(m_pWaitObjectsPool, pWaitObject); + + if (!m_bManualReset) + { + m_bSet = false; + break; + } + } + + sys_lwmutex_unlock(&m_staticMutex); + + return true; + +////////////////////////////////////////////////////////////// +#endif // NEW_WAIT_FOR_MULTIPLE_OBJECTS +////////////////////////////////////////////////////////////// +} + +//--------------------------------------------------------- + +bool CThreadEvent::Reset() +{ +#ifdef THREADS_DEBUG + AssertUseable(); +#endif +#ifdef _WIN32 + return ( ResetEvent( m_hSyncObject ) != 0 ); +#elif defined( _PS3 ) + + //Just mark us as no longer signaled + m_bSet = 0; + + return true; +#elif defined(POSIX) + pthread_mutex_lock( &m_Mutex ); + m_cSet = 0; + m_bWakeForEvent = false; + pthread_mutex_unlock( &m_Mutex ); + return true; +#endif +} + +//--------------------------------------------------------- + +bool CThreadEvent::Check() +{ + #ifdef _PS3 + return m_bSet; // Please, use for debugging only! + #endif +#ifdef THREADS_DEBUG + AssertUseable(); +#endif + return Wait( 0 ); +} + + + +bool CThreadEvent::Wait( uint32 dwTimeout ) +{ +////////////////////////////////////////////////////////////// +#ifndef NEW_WAIT_FOR_MULTIPLE_OBJECTS +////////////////////////////////////////////////////////////// + + +#if defined( _WIN32 ) || ( defined( POSIX ) && !defined( _PS3 ) ) + return CThreadSyncObject::Wait( dwTimeout ); +#elif defined( _PS3 ) + + { + + if (dwTimeout == 0) + { + //If timeout is 0 then just test it now (and reset it if manual ) + if (m_bSet) + { + if ( !m_bManualReset ) m_bSet=false; + return true; + } + return false; + } + + if (!AddWaitingThread()) + { + //Waiting thread NOT added because m_bSet was already set + if ( !m_bManualReset ) m_bSet=false; + return true; + } + + uint32 timeout; + int countTimeout = 0; + int ret = ETIMEDOUT; + while ( timeout=MIN(1, dwTimeout) ) + { + // on the PS3, "infinite timeout" is specified by zero, not + // 0xFFFFFFFF, so we need to perform that ternary here. +#error Untested code: + ret = sys_semaphore_wait( m_Semaphore, timeout == TT_INFINITE ? 0 : timeout * 1000 ); + Assert( (ret == CELL_OK) || (ret == ETIMEDOUT) ); + + if ( ret == CELL_OK ) + break; + + dwTimeout -= timeout; + countTimeout++; + if (countTimeout > 30) + { + printf("WARNING: possible deadlock in CThreadEvent::Wait() !!!\n"); + } + } + + RemoveWaitingThread(); + + if ( !m_bManualReset ) m_bSet=false; + + return ret == CELL_OK; + } + +#endif + +////////////////////////////////////////////////////////////// +#else // NEW_WAIT_FOR_MULTIPLE_OBJECTS +////////////////////////////////////////////////////////////// + + + CThreadEvent *pThis = this; + DWORD res = WaitForMultipleObjects(1, &pThis, true, dwTimeout); + return res == WAIT_OBJECT_0; + + +////////////////////////////////////////////////////////////// +#endif // NEW_WAIT_FOR_MULTIPLE_OBJECTS +////////////////////////////////////////////////////////////// +} + +#ifdef _WIN32 +//----------------------------------------------------------------------------- +// +// CThreadSemaphore +// +// To get Posix implementation, try http://www-128.ibm.com/developerworks/eserver/library/es-win32linux-sem.html +// +//----------------------------------------------------------------------------- + +CThreadSemaphore::CThreadSemaphore( int32 initialValue, int32 maxValue ) +{ +#ifdef _WIN32 + if ( maxValue ) + { + AssertMsg( maxValue > 0, "Invalid max value for semaphore" ); + AssertMsg( initialValue >= 0 && initialValue <= maxValue, "Invalid initial value for semaphore" ); + + m_hSyncObject = CreateSemaphore( NULL, initialValue, maxValue, NULL ); + + AssertMsg1(m_hSyncObject, "Failed to create semaphore (error 0x%x)", GetLastError()); + } + else + { + m_hSyncObject = NULL; + } +#elif defined( _PS3 ) + if ( maxValue ) + { + m_sema_max_val = maxValue; + m_semaCount = initialValue; + } +#endif +} + + +#ifdef _PS3 +//--------------------------------------------------------- + +bool CThreadSemaphore::AddWaitingThread() +{ + bool result; + + sys_lwmutex_lock(&m_staticMutex, 0); + + if (cellAtomicTestAndDecr32(&m_semaCount) > 0) + { + result=false; + } + else + { + result=true; + m_numWaitingThread++; + + if ( m_numWaitingThread == 1 ) + { + sys_semaphore_attribute_t semAttr; + sys_semaphore_attribute_initialize( semAttr ); + Assert(m_semaCount == 0); + int err = sys_semaphore_create( &m_Semaphore, &semAttr, 0, m_sema_max_val ); + Assert( err == CELL_OK ); + m_bInitalized = true; + } + } + + sys_lwmutex_unlock(&m_staticMutex); + return result; +} + +void CThreadSemaphore::RemoveWaitingThread() +{ + sys_lwmutex_lock(&m_staticMutex, 0); + + m_numWaitingThread--; + + if ( m_numWaitingThread == 0) + { + int err = sys_semaphore_destroy( m_Semaphore ); + Assert( err == CELL_OK ); + m_bInitalized = false; + } + + sys_lwmutex_unlock(&m_staticMutex); +} + +#endif + +#ifdef _PS3 + +bool CThreadSemaphore::Wait( uint32 dwTimeout ) +{ +#ifdef THREADS_DEBUG + AssertUseable(); +#endif + + +#ifndef NO_THREAD_SYNC + if (!AddWaitingThread()) + { + //Waiting thread NOT added because semaphore was already in a signaled state + return true; + } + + int ret = sys_semaphore_wait( m_Semaphore, dwTimeout == TT_INFINITE ? 0 : dwTimeout * 1000 ); + Assert( (ret == CELL_OK) || (ret == ETIMEDOUT) ); + + RemoveWaitingThread(); + + int old = cellAtomicDecr32(&m_semaCount); + Assert(old>0); +#else + int ret = CELL_OK; +#endif + + // sys_ppu_thread_yield doesn't seem to function properly, so sleep instead. + sys_timer_usleep( 60 ); + + + + return ret == CELL_OK; +} + +#endif + +//--------------------------------------------------------- + +bool CThreadSemaphore::Release( int32 releaseCount, int32 *pPreviousCount ) +{ +#ifdef THRDTOOL_DEBUG + AssertUseable(); +#endif +#ifdef _WIN32 + return ( ReleaseSemaphore( m_hSyncObject, releaseCount, (LPLONG)pPreviousCount ) != 0 ); +#elif defined( _PS3 ) + +#ifndef NO_THREAD_SYNC + + if (m_bInitalized) + { + sys_semaphore_value_t previousVal; + sys_semaphore_get_value( m_Semaphore, &previousVal ); + + cellAtomicAdd32(&m_semaCount, releaseCount); + + *pPreviousCount = previousVal; + + int err = sys_semaphore_post( m_Semaphore, releaseCount ); + Assert(err == CELL_OK); + } + +#endif + + return true; +#endif +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +CThreadFullMutex::CThreadFullMutex( bool bEstablishInitialOwnership, const char *pszName ) +{ + m_hSyncObject = CreateMutex( NULL, bEstablishInitialOwnership, pszName ); + + AssertMsg1( m_hSyncObject, "Failed to create mutex (error 0x%x)", GetLastError() ); +} + +//--------------------------------------------------------- + +bool CThreadFullMutex::Release() +{ +#ifdef THRDTOOL_DEBUG + AssertUseable(); +#endif + return ( ReleaseMutex( m_hSyncObject ) != 0 ); +} + +#endif + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +#if ( defined(WIN32) || defined(OSX) ) && !defined(PLATFORM_PS3) +namespace GenericThreadLocals +{ +CThreadLocalBase::CThreadLocalBase() +{ +#ifdef _WIN32 + m_index = TlsAlloc(); + AssertMsg( m_index != 0xFFFFFFFF, "Bad thread local" ); + if ( m_index == 0xFFFFFFFF ) + Error( "Out of thread local storage!\n" ); +#elif defined(POSIX) + if ( pthread_key_create( (pthread_key_t *)&m_index, NULL ) != 0 ) + Error( "Out of thread local storage!\n" ); +#endif +} + +//--------------------------------------------------------- + +CThreadLocalBase::~CThreadLocalBase() +{ +#ifdef _WIN32 + if ( m_index != 0xFFFFFFFF ) + TlsFree( m_index ); + m_index = 0xFFFFFFFF; +#elif defined(POSIX) + pthread_key_delete( m_index ); +#endif +} + +//--------------------------------------------------------- + +void * CThreadLocalBase::Get() const +{ +#ifdef _WIN32 + if ( m_index != 0xFFFFFFFF ) + return TlsGetValue( m_index ); + AssertMsg( 0, "Bad thread local" ); + return NULL; +#elif defined(POSIX) + void *value = pthread_getspecific( m_index ); + return value; +#endif +} + +//--------------------------------------------------------- + +void CThreadLocalBase::Set( void *value ) +{ +#ifdef _WIN32 + if (m_index != 0xFFFFFFFF) + TlsSetValue(m_index, value); + else + AssertMsg( 0, "Bad thread local" ); +#elif defined(POSIX) + if ( pthread_setspecific( m_index, value ) != 0 ) + AssertMsg( 0, "Bad thread local" ); +#endif +} +} // namespace GenericThreadLocals +#endif // ( defined(WIN32) || defined(POSIX) ) && !defined(PLATFORM_PS3) +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- + +#ifdef MSVC +//#ifdef _X360 +#define TO_INTERLOCK_PARAM(p) ((volatile long *)p) +#define TO_INTERLOCK_PTR_PARAM(p) ((void **)p) +//#else +//#define TO_INTERLOCK_PARAM(p) (p) +//#define TO_INTERLOCK_PTR_PARAM(p) (p) +//#endif + +#if !defined(USE_INTRINSIC_INTERLOCKED) && !defined(_X360) +int32 ThreadInterlockedIncrement( int32 volatile *pDest ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedIncrement( TO_INTERLOCK_PARAM(pDest) ); +} + +int32 ThreadInterlockedDecrement( int32 volatile *pDest ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedDecrement( TO_INTERLOCK_PARAM(pDest) ); +} + +int32 ThreadInterlockedExchange( int32 volatile *pDest, int32 value ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedExchange( TO_INTERLOCK_PARAM(pDest), value ); +} + +int32 ThreadInterlockedExchangeAdd( int32 volatile *pDest, int32 value ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedExchangeAdd( TO_INTERLOCK_PARAM(pDest), value ); +} + +int32 ThreadInterlockedCompareExchange( int32 volatile *pDest, int32 value, int32 comperand ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedCompareExchange( TO_INTERLOCK_PARAM(pDest), value, comperand ); +} + +bool ThreadInterlockedAssignIf( int32 volatile *pDest, int32 value, int32 comperand ) +{ + Assert( (size_t)pDest % 4 == 0 ); + +#if !(defined(_WIN64) || defined (_X360)) + __asm + { + mov eax,comperand + mov ecx,pDest + mov edx,value + lock cmpxchg [ecx],edx + mov eax,0 + setz al + } +#else + return ( InterlockedCompareExchange( TO_INTERLOCK_PARAM(pDest), value, comperand ) == comperand ); +#endif +} + +#endif + +#if !defined( USE_INTRINSIC_INTERLOCKED ) || defined( _WIN64 ) +void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedExchangePointer( TO_INTERLOCK_PTR_PARAM(pDest), value ); +} + +void *ThreadInterlockedCompareExchangePointer( void * volatile *pDest, void *value, void *comperand ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedCompareExchangePointer( TO_INTERLOCK_PTR_PARAM(pDest), value, comperand ); +} + +bool ThreadInterlockedAssignPointerIf( void * volatile *pDest, void *value, void *comperand ) +{ + Assert( (size_t)pDest % 4 == 0 ); +#if !(defined(_WIN64) || defined (_X360)) + __asm + { + mov eax,comperand + mov ecx,pDest + mov edx,value + lock cmpxchg [ecx],edx + mov eax,0 + setz al + } +#else + return ( InterlockedCompareExchangePointer( TO_INTERLOCK_PTR_PARAM(pDest), value, comperand ) == comperand ); +#endif +} +#endif + +int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) +{ + Assert( (size_t)pDest % 8 == 0 ); + +#if defined(_WIN64) || defined (_X360) + return InterlockedCompareExchange64( pDest, value, comperand ); +#else + __asm + { + lea esi,comperand; + lea edi,value; + + mov eax,[esi]; + mov edx,4[esi]; + mov ebx,[edi]; + mov ecx,4[edi]; + mov esi,pDest; + lock CMPXCHG8B [esi]; + } +#endif +} + +bool ThreadInterlockedAssignIf64(volatile int64 *pDest, int64 value, int64 comperand ) +{ + Assert( (size_t)pDest % 8 == 0 ); + +#if defined(_X360) || defined(_WIN64) + return ( ThreadInterlockedCompareExchange64( pDest, value, comperand ) == comperand ); +#else + __asm + { + lea esi,comperand; + lea edi,value; + + mov eax,[esi]; + mov edx,4[esi]; + mov ebx,[edi]; + mov ecx,4[edi]; + mov esi,pDest; + lock CMPXCHG8B [esi]; + mov eax,0; + setz al; + } +#endif +} + +#elif defined(GNUC) + +#ifdef OSX +#include <libkern/OSAtomic.h> +#endif + + +long ThreadInterlockedIncrement( long volatile *pDest ) +{ + return __sync_fetch_and_add( pDest, 1 ) + 1; +} + +long ThreadInterlockedDecrement( long volatile *pDest ) +{ + return __sync_fetch_and_sub( pDest, 1 ) - 1; +} + +long ThreadInterlockedExchange( long volatile *pDest, long value ) +{ + return __sync_lock_test_and_set( pDest, value ); +} + +long ThreadInterlockedExchangeAdd( long volatile *pDest, long value ) +{ + return __sync_fetch_and_add( pDest, value ); +} + +long ThreadInterlockedCompareExchange( long volatile *pDest, long value, long comperand ) +{ + return __sync_val_compare_and_swap( pDest, comperand, value ); +} + +bool ThreadInterlockedAssignIf( long volatile *pDest, long value, long comperand ) +{ + return __sync_bool_compare_and_swap( pDest, comperand, value ); +} + +#if !defined( USE_INTRINSIC_INTERLOCKED ) +void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) +{ + return __sync_lock_test_and_set( pDest, value ); +} + +void *ThreadInterlockedCompareExchangePointer( void *volatile *pDest, void *value, void *comperand ) +{ + return __sync_val_compare_and_swap( pDest, comperand, value ); +} + +bool ThreadInterlockedAssignPointerIf( void * volatile *pDest, void *value, void *comperand ) +{ + return __sync_bool_compare_and_swap( pDest, comperand, value ); +} +#endif + +int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) +{ +#if defined(OSX) + int64 retVal = *pDest; + if ( OSAtomicCompareAndSwap64( comperand, value, pDest ) ) + retVal = *pDest; + + return retVal; +#else + return __sync_val_compare_and_swap( pDest, comperand, value ); +#endif +} + +bool ThreadInterlockedAssignIf64( int64 volatile * pDest, int64 value, int64 comperand ) +{ + return __sync_bool_compare_and_swap( pDest, comperand, value ); +} + + +#elif defined( _PS3 ) + +// This is defined in the header! + +#else +// This will perform horribly, +#error "Falling back to mutexed interlocked operations, you really don't have intrinsics you can use?"ß +CThreadMutex g_InterlockedMutex; + +long ThreadInterlockedIncrement( long volatile *pDest ) +{ + AUTO_LOCK( g_InterlockedMutex ); + return ++(*pDest); +} + +long ThreadInterlockedDecrement( long volatile *pDest ) +{ + AUTO_LOCK( g_InterlockedMutex ); + return --(*pDest); +} + +long ThreadInterlockedExchange( long volatile *pDest, long value ) +{ + AUTO_LOCK( g_InterlockedMutex ); + long retVal = *pDest; + *pDest = value; + return retVal; +} + +void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) +{ + AUTO_LOCK( g_InterlockedMutex ); + void *retVal = *pDest; + *pDest = value; + return retVal; +} + +long ThreadInterlockedExchangeAdd( long volatile *pDest, long value ) +{ + AUTO_LOCK( g_InterlockedMutex ); + long retVal = *pDest; + *pDest += value; + return retVal; +} + +long ThreadInterlockedCompareExchange( long volatile *pDest, long value, long comperand ) +{ + AUTO_LOCK( g_InterlockedMutex ); + long retVal = *pDest; + if ( *pDest == comperand ) + *pDest = value; + return retVal; +} + +void *ThreadInterlockedCompareExchangePointer( void * volatile *pDest, void *value, void *comperand ) +{ + AUTO_LOCK( g_InterlockedMutex ); + void *retVal = *pDest; + if ( *pDest == comperand ) + *pDest = value; + return retVal; +} + + +int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) +{ + Assert( (size_t)pDest % 8 == 0 ); + AUTO_LOCK( g_InterlockedMutex ); + int64 retVal = *pDest; + if ( *pDest == comperand ) + *pDest = value; + return retVal; +} + +#endif + +int64 ThreadInterlockedExchange64( int64 volatile *pDest, int64 value ) +{ + Assert( (size_t)pDest % 8 == 0 ); + int64 Old; + + do + { + Old = *pDest; + } while (ThreadInterlockedCompareExchange64(pDest, value, Old) != Old); + + return Old; +} + + +//----------------------------------------------------------------------------- + +#if defined(_WIN32) && defined(THREAD_PROFILER) +void ThreadNotifySyncNoop(void *p) {} + +#define MAP_THREAD_PROFILER_CALL( from, to ) \ + void from(void *p) \ + { \ + static CDynamicFunction<void (*)(void *)> dynFunc( "libittnotify.dll", #to, ThreadNotifySyncNoop ); \ + (*dynFunc)(p); \ + } + +MAP_THREAD_PROFILER_CALL( ThreadNotifySyncPrepare, __itt_notify_sync_prepare ); +MAP_THREAD_PROFILER_CALL( ThreadNotifySyncCancel, __itt_notify_sync_cancel ); +MAP_THREAD_PROFILER_CALL( ThreadNotifySyncAcquired, __itt_notify_sync_acquired ); +MAP_THREAD_PROFILER_CALL( ThreadNotifySyncReleasing, __itt_notify_sync_releasing ); + +#endif + +//----------------------------------------------------------------------------- +// +// CThreadMutex +// +//----------------------------------------------------------------------------- + +#ifdef _PS3 +CThreadMutex::CThreadMutex() +{ + // sys_mutex with recursion enabled is like a win32 critical section + sys_mutex_attribute_t mutexAttr; + sys_mutex_attribute_initialize( mutexAttr ); + mutexAttr.attr_recursive = SYS_SYNC_RECURSIVE; + sys_mutex_create( &m_Mutex, &mutexAttr ); +} +CThreadMutex::~CThreadMutex() +{ + sys_mutex_destroy( m_Mutex ); +} +#elif !defined( POSIX ) +CThreadMutex::CThreadMutex() +{ +#ifdef THREAD_MUTEX_TRACING_ENABLED + memset( &m_CriticalSection, 0, sizeof(m_CriticalSection) ); +#endif + InitializeCriticalSectionAndSpinCount((CRITICAL_SECTION *)&m_CriticalSection, 4000); +#ifdef THREAD_MUTEX_TRACING_SUPPORTED + // These need to be initialized unconditionally in case mixing release & debug object modules + // Lock and unlock may be emitted as COMDATs, in which case may get spurious output + m_currentOwnerID = m_lockCount = 0; + m_bTrace = false; +#endif +} + +CThreadMutex::~CThreadMutex() +{ + DeleteCriticalSection((CRITICAL_SECTION *)&m_CriticalSection); +} +#endif // !POSIX + +#ifdef IS_WINDOWS_PC +typedef BOOL (WINAPI*TryEnterCriticalSectionFunc_t)(LPCRITICAL_SECTION); +static CDynamicFunction<TryEnterCriticalSectionFunc_t> DynTryEnterCriticalSection( "Kernel32.dll", "TryEnterCriticalSection" ); +#elif defined( _X360 ) +#define DynTryEnterCriticalSection TryEnterCriticalSection +#endif + +bool CThreadMutex::TryLock() +{ +#if defined( MSVC ) +#ifdef THREAD_MUTEX_TRACING_ENABLED + uint thisThreadID = ThreadGetCurrentId(); + if ( m_bTrace && m_currentOwnerID && ( m_currentOwnerID != thisThreadID ) ) + Msg( "Thread %u about to try-wait for lock %x owned by %u\n", ThreadGetCurrentId(), (CRITICAL_SECTION *)&m_CriticalSection, m_currentOwnerID ); +#endif + if ( DynTryEnterCriticalSection != NULL ) + { + if ( (*DynTryEnterCriticalSection )( (CRITICAL_SECTION *)&m_CriticalSection ) != FALSE ) + { +#ifdef THREAD_MUTEX_TRACING_ENABLED + if (m_lockCount == 0) + { + // we now own it for the first time. Set owner information + m_currentOwnerID = thisThreadID; + if ( m_bTrace ) + Msg( "Thread %u now owns lock 0x%x\n", m_currentOwnerID, (CRITICAL_SECTION *)&m_CriticalSection ); + } + m_lockCount++; +#endif + return true; + } + return false; + } + Lock(); + return true; +#elif defined( _PS3 ) + +#ifndef NO_THREAD_SYNC + if ( sys_mutex_trylock( m_Mutex ) == CELL_OK ) +#endif + + return true; + + return false; // ?? moved from EA code + +#elif defined( POSIX ) + return pthread_mutex_trylock( &m_Mutex ) == 0; +#else +#error "Implement me!" + return true; +#endif +} + +//----------------------------------------------------------------------------- +// +// CThreadFastMutex +// +//----------------------------------------------------------------------------- + +#ifdef THREAD_FAST_MUTEX_TIMINGS +// This is meant to be used in combination with breakpoints and in-debugee, so we turn the optimizer off +#pragma optimize( "", off ) +CThreadFastMutex *g_pIgnoredMutexes[256]; // Ignore noisy non-problem mutex. Probably could be an array. Right now needed only for sound thread +float g_MutexTimingTolerance = 5; +bool g_bMutexTimingOutput; + +void TrapMutexTimings( uint32 probableBlocker, uint32 thisThread, volatile CThreadFastMutex *pMutex, CFastTimer &spikeTimer, CAverageCycleCounter &sleepTimer ) +{ + spikeTimer.End(); + if ( spikeTimer.GetDuration().GetMillisecondsF() > g_MutexTimingTolerance ) + { + bool bIgnore = false; + for ( int j = 0; j < ARRAYSIZE( g_pIgnoredMutexes ) && g_pIgnoredMutexes[j]; j++ ) + { + if ( g_pIgnoredMutexes[j] == pMutex ) + { + bIgnore = true; + break; + } + } + + if ( !bIgnore && spikeTimer.GetDuration().GetMillisecondsF() < 100 ) + { + volatile float FastMutexDuration = spikeTimer.GetDuration().GetMillisecondsF(); + volatile float average = sleepTimer.GetAverageMilliseconds(); + volatile float peak = sleepTimer.GetPeakMilliseconds(); volatile int xx = 6; + if ( g_bMutexTimingOutput ) + { + char szBuf[256]; + Msg( "M (%.8x): [%.8x <-- %.8x] (%f,%f,%f)\n", pMutex, probableBlocker, thisThread, FastMutexDuration, average, peak ); + } + } + } +} + +#else +#define TrapMutexTimings( a, b, c, d, e ) ((void)0) +#endif + +//------------------------------------- + +#define THREAD_SPIN (8*1024) + +void CThreadFastMutex::Lock( const uint32 threadId, unsigned nSpinSleepTime ) volatile +{ +#ifdef THREAD_FAST_MUTEX_TIMINGS + CAverageCycleCounter sleepTimer; + CFastTimer spikeTimer; + uint32 currentOwner = m_ownerID; + spikeTimer.Start(); + sleepTimer.Init(); +#endif + int i; + if ( nSpinSleepTime != TT_INFINITE ) + { + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLock( threadId ) ) + { + TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); + return; + } + ThreadPause(); + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLock( threadId ) ) + { + TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); + return; + } + ThreadPause(); + if ( i % 1024 == 0 ) + { +#ifdef THREAD_FAST_MUTEX_TIMINGS + CAverageTimeMarker marker( &sleepTimer ); +#endif + ThreadSleep( 0 ); + } + } + +#ifdef _WIN32 + if ( !nSpinSleepTime && GetThreadPriority( GetCurrentThread() ) > THREAD_PRIORITY_NORMAL ) + { + nSpinSleepTime = 1; + } +#endif + + if ( nSpinSleepTime ) + { + for ( i = THREAD_SPIN; i != 0; --i ) + { +#ifdef THREAD_FAST_MUTEX_TIMINGS + CAverageTimeMarker marker( &sleepTimer ); +#endif + if ( TryLock( threadId ) ) + { + TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); + return; + } + + ThreadPause(); + ThreadSleep( 0 ); + } + + } + + for ( ;; ) + { +#ifdef THREAD_FAST_MUTEX_TIMINGS + CAverageTimeMarker marker( &sleepTimer ); +#endif + if ( TryLock( threadId ) ) + { + TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); + return; + } + + ThreadPause(); + ThreadSleep( nSpinSleepTime ); + } + } + else + { + for ( ;; ) + { + if ( TryLock( threadId ) ) + { + TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); + return; + } + + ThreadPause(); + } + } +} + +#ifdef THREAD_FAST_MUTEX_TIMINGS +#pragma optimize( "", on ) +#endif + +//----------------------------------------------------------------------------- +// +// CThreadRWLock +// +//----------------------------------------------------------------------------- + +void CThreadRWLock::WaitForRead() +{ + m_nPendingReaders++; + + do + { + m_mutex.Unlock(); + m_CanRead.Wait(); + m_mutex.Lock(); + } + while (m_nWriters); + + m_nPendingReaders--; +} + + +void CThreadRWLock::LockForWrite() +{ + m_mutex.Lock(); + bool bWait = ( m_nWriters != 0 || m_nActiveReaders != 0 ); + m_nWriters++; + m_CanRead.Reset(); + m_mutex.Unlock(); + + if ( bWait ) + { + m_CanWrite.Wait(); + } +} + +void CThreadRWLock::UnlockWrite() +{ + m_mutex.Lock(); + m_nWriters--; + if ( m_nWriters == 0) + { + if ( m_nPendingReaders ) + { + m_CanRead.Set(); + } + } + else + { + m_CanWrite.Set(); + } + m_mutex.Unlock(); +} + +//----------------------------------------------------------------------------- +// +// CThreadSpinRWLock +// +//----------------------------------------------------------------------------- +#ifndef OLD_SPINRWLOCK + +void CThreadSpinRWLock::SpinLockForWrite() +{ + int i; + + if ( TryLockForWrite_UnforcedInline() ) + { + return; + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForWrite_UnforcedInline() ) + { + return; + } + ThreadPause(); + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForWrite_UnforcedInline() ) + { + return; + } + ThreadPause(); + if ( i % 1024 == 0 ) + { + ThreadSleep( 0 ); + } + } + + for ( i = THREAD_SPIN * 4; i != 0; --i ) + { + if ( TryLockForWrite_UnforcedInline() ) + { + return; + } + + ThreadPause(); + ThreadSleep( 0 ); + } + + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if ( TryLockForWrite_UnforcedInline() ) + { + return; + } + + ThreadPause(); + ThreadSleep( 1 ); + } +} + +void CThreadSpinRWLock::SpinLockForRead() +{ + int i; + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForRead_UnforcedInline() ) + { + return; + } + ThreadPause(); + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForRead_UnforcedInline() ) + { + return; + } + ThreadPause(); + if ( i % 1024 == 0 ) + { + ThreadSleep( 0 ); + } + } + + for ( i = THREAD_SPIN * 4; i != 0; --i ) + { + if ( TryLockForRead_UnforcedInline() ) + { + return; + } + + ThreadPause(); + ThreadSleep( 0 ); + } + + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if ( TryLockForRead_UnforcedInline() ) + { + return; + } + + ThreadPause(); + ThreadSleep( 1 ); + } +} + +#else +/* (commented out to reduce distraction in colorized editor, remove entirely when new implementation settles) +void CThreadSpinRWLock::SpinLockForWrite( const uint32 threadId ) +{ + int i; + + if ( TryLockForWrite( threadId ) ) + { + return; + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForWrite( threadId ) ) + { + return; + } + ThreadPause(); + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForWrite( threadId ) ) + { + return; + } + ThreadPause(); + if ( i % 1024 == 0 ) + { + ThreadSleep( 0 ); + } + } + + for ( i = THREAD_SPIN * 4; i != 0; --i ) + { + if ( TryLockForWrite( threadId ) ) + { + return; + } + + ThreadPause(); + ThreadSleep( 0 ); + } + + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if ( TryLockForWrite( threadId ) ) + { + return; + } + + ThreadPause(); + ThreadSleep( 1 ); + } +} + +void CThreadSpinRWLock::LockForRead() +{ + int i; + if ( TryLockForRead() ) + { + return; + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForRead() ) + { + return; + } + ThreadPause(); + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForRead() ) + { + return; + } + ThreadPause(); + if ( i % 1024 == 0 ) + { + ThreadSleep( 0 ); + } + } + + for ( i = THREAD_SPIN * 4; i != 0; --i ) + { + if ( TryLockForRead() ) + { + return; + } + + ThreadPause(); + ThreadSleep( 0 ); + } + + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if ( TryLockForRead() ) + { + return; + } + + ThreadPause(); + ThreadSleep( 1 ); + } +} + +void CThreadSpinRWLock::UnlockRead() +{ + int i; + + Assert( m_lockInfo.m_nReaders > 0 && m_lockInfo.m_writerId == 0 ); + + //uint32 nLockInfoReaders = m_lockInfo.m_nReaders; + LockInfo_t oldValue; + LockInfo_t newValue; + + if( IsX360() ) + { + // this is the code equivalent to original code (see below) that doesn't cause LHS on Xbox360 + // WARNING: This code assumes BIG Endian CPU + oldValue.m_i64 = uint32( m_lockInfo.m_nReaders ); + newValue.m_i64 = oldValue.m_i64 - 1; // NOTE: when we have -1 (or 0xFFFFFFFF) readers, this will result in non-equivalent code + } + else + { + // this is the original code that worked here for a while + oldValue.m_nReaders = m_lockInfo.m_nReaders; + oldValue.m_writerId = 0; + newValue.m_nReaders = oldValue.m_nReaders - 1; + newValue.m_writerId = 0; + } + ThreadMemoryBarrier(); + if( AssignIf( newValue, oldValue ) ) + return; + + ThreadPause(); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders - 1; + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if( AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders - 1; + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if( AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + if ( i % 512 == 0 ) + { + ThreadSleep( 0 ); + } + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders - 1; + } + + for ( i = THREAD_SPIN * 4; i != 0; --i ) + { + if( AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + ThreadSleep( 0 ); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders - 1; + } + + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if( AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + ThreadSleep( 1 ); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders - 1; + } +} + +void CThreadSpinRWLock::UnlockWrite() +{ + Assert( m_lockInfo.m_writerId == ThreadGetCurrentId() && m_lockInfo.m_nReaders == 0 ); + static const LockInfo_t newValue = { { 0, 0 } }; + ThreadMemoryBarrier(); + ThreadInterlockedExchange64( (int64 *)&m_lockInfo, *((int64 *)&newValue) ); + m_nWriters--; +} +*/ +#endif + +#if defined( _PS3 ) +// All CThread code is inline in the header for PS3 + +// This function is implemented here rather than the header because g_pCurThread resolves to GetCurThread() on PS3 +// and we don't want to create a dependency on the ELF stub for everyone who includes the header. +PLATFORM_INTERFACE CThread *GetCurThreadPS3() +{ + return (CThread*)g_pCurThread; +} + +PLATFORM_INTERFACE void SetCurThreadPS3( CThread *pThread ) +{ + g_pCurThread = pThread; +} +#else +// The CThread implementation needs to be inlined for performance on the PS3 - It makes a difference of more than 1ms/frame +// for other platforms, we include the .inl in the .cpp file where it existed before +#include "../public/tier0/threadtools.inl" +#endif + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CWorkerThread::CWorkerThread() +: m_EventSend(true), // must be manual-reset for PeekCall() + m_EventComplete(true), // must be manual-reset to handle multiple wait with thread properly + m_Param(0), + m_ReturnVal(0) +{ +} + +//--------------------------------------------------------- + +int CWorkerThread::CallWorker(unsigned dw, unsigned timeout, bool fBoostWorkerPriorityToMaster) +{ + return Call(dw, timeout, fBoostWorkerPriorityToMaster); +} + +//--------------------------------------------------------- + +int CWorkerThread::CallMaster(unsigned dw, unsigned timeout) +{ + return Call(dw, timeout, false); +} + +//--------------------------------------------------------- + +CThreadEvent &CWorkerThread::GetCallHandle() +{ + return m_EventSend; +} + +//--------------------------------------------------------- + +unsigned CWorkerThread::GetCallParam() const +{ + return m_Param; +} + +//--------------------------------------------------------- + +int CWorkerThread::BoostPriority() +{ + int iInitialPriority = GetPriority(); + +#ifdef WIN32 + const int iNewPriority = ThreadGetPriority( GetThreadHandle() ); + if (iNewPriority > iInitialPriority) + ThreadSetPriority( GetThreadHandle(), iNewPriority); +#elif !defined( _PS3 ) + const int iNewPriority = ThreadGetPriority( (ThreadHandle_t)GetThreadID() ); + if (iNewPriority > iInitialPriority) + ThreadSetPriority( (ThreadHandle_t)GetThreadID(), iNewPriority); +#endif + + return iInitialPriority; +} + +//--------------------------------------------------------- +static uint32 DefaultWaitFunc( uint32 nHandles, CThreadEvent** ppHandles, int bWaitAll, uint32 timeout ) +{ + return CThreadEvent::WaitForMultiple( nHandles, ppHandles, bWaitAll!=0, timeout ) ; +} + + +int CWorkerThread::Call(unsigned dwParam, unsigned timeout, bool fBoostPriority, WaitFunc_t waitFunc) +{ + AssertMsg(!m_EventSend.Check(), "Cannot perform call if there's an existing call pending" ); + + AUTO_LOCK( m_Lock ); + + if (!IsAlive()) + return WTCR_FAIL; + + int iInitialPriority = 0; + if (fBoostPriority) + { + iInitialPriority = BoostPriority(); + } + + // set the parameter, signal the worker thread, wait for the completion to be signaled + m_Param = dwParam; + + m_EventComplete.Reset(); + m_EventSend.Set(); + + WaitForReply( timeout, waitFunc ); + + if (fBoostPriority) + SetPriority(iInitialPriority); + + return m_ReturnVal; +} + +//--------------------------------------------------------- +// +// Wait for a request from the client +// +//--------------------------------------------------------- +int CWorkerThread::WaitForReply( unsigned timeout ) +{ + return WaitForReply( timeout, NULL ); +} + +int CWorkerThread::WaitForReply( unsigned timeout, WaitFunc_t pfnWait ) +{ + if (!pfnWait) + { + pfnWait = &DefaultWaitFunc; + } + + CThreadEvent *waits[] = + { + &m_EventComplete, + &m_ExitEvent + }; + + unsigned result; + bool bInDebugger = Plat_IsInDebugSession(); + + uint32 dwActualTimeout = ( (timeout==TT_INFINITE) ? 30000 : timeout ); + + do + { +#ifdef WIN32 + // Make sure the thread handle hasn't been closed + if ( !GetThreadHandle() ) + { + result = 1; + break; + } +#endif + + result = (*pfnWait)( ARRAYSIZE( waits ), waits, false, dwActualTimeout ); + + AssertMsg(timeout != TT_INFINITE || result != TW_TIMEOUT, "Possible hung thread, call to thread timed out"); + + } while ( bInDebugger && ( timeout == TT_INFINITE && result == TW_TIMEOUT ) ); + + if ( result != 0 ) + { + if (result == TW_TIMEOUT) + { + m_ReturnVal = WTCR_TIMEOUT; + } + else if (result == 1) + { + DevMsg( 2, "Thread failed to respond, probably exited\n"); + m_EventSend.Reset(); + m_ReturnVal = WTCR_TIMEOUT; + } + else + { + m_EventSend.Reset(); + m_ReturnVal = WTCR_THREAD_GONE; + } + } + + return m_ReturnVal; +} + + +//--------------------------------------------------------- +// +// Wait for a request from the client +// +//--------------------------------------------------------- + +bool CWorkerThread::WaitForCall(unsigned * pResult) +{ + return WaitForCall(TT_INFINITE, pResult); +} + +//--------------------------------------------------------- + +bool CWorkerThread::WaitForCall(unsigned dwTimeout, unsigned * pResult) +{ + bool returnVal = m_EventSend.Wait(dwTimeout); + if (pResult) + *pResult = m_Param; + return returnVal; +} + +//--------------------------------------------------------- +// +// is there a request? +// + +bool CWorkerThread::PeekCall(unsigned * pParam) +{ + if (!m_EventSend.Check()) + { + return false; + } + else + { + if (pParam) + { + *pParam = m_Param; + } + return true; + } +} + +//--------------------------------------------------------- +// +// Reply to the request +// + +void CWorkerThread::Reply(unsigned dw) +{ + m_Param = 0; + m_ReturnVal = dw; + + // The request is now complete so PeekCall() should fail from + // now on + // + // This event should be reset BEFORE we signal the client + m_EventSend.Reset(); + + // Tell the client we're finished + m_EventComplete.Set(); +} + + +//----------------------------------------------------------------------------- + + +#if defined( _PS3 ) + +/******************************************************************************* +* PS3 equivalent to Win32 function for setting events +*******************************************************************************/ +BOOL SetEvent( CThreadEvent *pEvent ) +{ + bool bRetVal = pEvent->Set(); + if ( !bRetVal ) + Assert(0); + + return bRetVal; +} + +/******************************************************************************* +* PS3 equivalent to Win32 function for resetting events +*******************************************************************************/ +BOOL ResetEvent( CThreadEvent *pEvent ) +{ + return pEvent->Reset(); +} + +#define MAXIMUM_WAIT_OBJECTS 64 + +/******************************************************************************* +* Wait for a selection of events to terminate +*******************************************************************************/ +DWORD WaitForMultipleObjects( DWORD nCount, CThreadEvent **lppHandles, BOOL bWaitAll, DWORD dwMilliseconds ) +{ + ////////////////////////////////////////////////////////////// +#ifndef NEW_WAIT_FOR_MULTIPLE_OBJECTS + ////////////////////////////////////////////////////////////// + + + // Support for a limited amount of events + if ( nCount >= MAXIMUM_WAIT_OBJECTS ) + { + Assert(0); + return false; + } + + bool bRunning = true; + unsigned int result = TW_FAILED; + + // For bWaitAll + int numEvent = 0; + int eventComplete[ MAXIMUM_WAIT_OBJECTS ] = {0}; + + uint64_t timeDiffMS = 0; + uint64_t startTimeMS = Plat_MSTime(); + uint64_t endTimeMS = 0; + + while ( bRunning ) + { + // Check for a timeout + if ( bRunning && ( dwMilliseconds != INFINITE ) && ( timeDiffMS > dwMilliseconds ) ) + { + result = TW_TIMEOUT; + bRunning = false; + } + + // Wait for all the events to be set + if ( bWaitAll ) + { + for ( int event = 0; event < nCount; ++event ) + { + if ( lppHandles[event]->Wait(1) ) + { + // If an event is complete, mark it as complete in our list + if ( eventComplete[ event ] == 0 ) + { + numEvent++; + eventComplete[ event ] = 1; + } + } + } + + // If all the events have been set, terminate the function + if ( numEvent >= nCount ) + { + result = WAIT_OBJECT_0; + bRunning = false; + } + } + + // Wait for one event to be set + else + { + for ( int event = 0; event < nCount; ++event ) + { + if ( lppHandles[event]->Wait(1) ) + { + result = WAIT_OBJECT_0 + event; + bRunning = false; + break; + } + } + } + + endTimeMS = Plat_MSTime(); + timeDiffMS = endTimeMS - startTimeMS; + } + + return result; + + + + ////////////////////////////////////////////////////////////// +#else // NEW_WAIT_FOR_MULTIPLE_OBJECTS // (expected PS3 only) + ////////////////////////////////////////////////////////////// +#ifndef _PS3 +#error This code was written expecting to be run on PS3. +#endif + + // check if we have a wait objects semaphore + if (!gbWaitObjectsCreated) + { + sys_semaphore_attribute_t semAttr; + sys_semaphore_attribute_initialize(semAttr); + sys_semaphore_create(&gWaitObjectsSemaphore, &semAttr, 0, 0xFFFF); + + gbWaitObjectsCreated = true; + } + + // Support for a limited amount of events + if ( nCount >= MAXIMUM_WAIT_OBJECTS ) + { + Assert(0); + return false; + } + + unsigned int result = WAIT_FAILED; + int res = CELL_OK; + int event = -1; + int numEvent = 0; + + // run through events registering this thread with each one + for (int i = 0; i < nCount; i++) + { + lppHandles[i]->RegisterWaitingThread(&gWaitObjectsSemaphore, i, &event); + } + + + // in the Source API, a timeOut of 0 means very short timeOut, not (as in the PS3 spec) an infinite timeout. + // TT_INFINITE is #defined to 2^31-1, which means "infinite timeout" on PC and "72 minutes, 35 seconds" on PS3. + // conversely, the code below (around deltaTime) expects to be able to compare against the timeout + // value given here, so we cannot just replace 0 with 1 and TT_INFINITE with 0. + // So, we replace 0 with 1, meaning "a very short time", and test for the special value TT_INFINITE + // at the moment of calling sys_semaphore_wait, where we replace it with the real "infinite timeout" + // value. It isn't safe to simply increase the declaration size of TT_INFINITE, because as you can + // see it is often assigned to uint32s. + // Also, Source timeouts are specified in milliseconds, and PS3 timeouts are in microseconds, + // so we need to multiply by one thousand. + uint32 timeOut = dwMilliseconds; + if ( timeOut == 0 ) + { + timeOut = 1; + } + else if ( timeOut != TT_INFINITE ) + { + timeOut *= 1000; + // note that it's impossible for dwMilliseconds * 1000 + // to coincidentally equal TT_INFINITE since TT_INFINITE + // is not divisible by 1000. + COMPILE_TIME_ASSERT( TT_INFINITE % 1000 != 0 ); + } + + COMPILE_TIME_ASSERT( TT_INFINITE != 0 ); // The code here was written expecting (working around) that TT_INFINITE is + // MAXINT, so if you changed this number, please read the comment above and + // carefully examine the code here to make sure that timeouts still work + // correctly on PS3. Be aware that in many places in Source, a timeout of + // 0 has some special meaning other than "infinite timeout", so track those + // down too. + + + // Wait for all the events to be set + if ( bWaitAll ) + { + while (numEvent < nCount) + { + uint64_t deltaTime = Timer_GetTimeUS(); + + res = sys_semaphore_wait(gWaitObjectsSemaphore, timeOut == TT_INFINITE ? 0 : timeOut ); + + deltaTime = Timer_GetTimeUS() - deltaTime; + + if (res == ETIMEDOUT) + { + result = TW_TIMEOUT; + break; + } + else if (res == CELL_OK) + { + numEvent++; + + if (deltaTime >= timeOut) + { + // note - if this is not truly a time out + // then it will be set to WAIT_OBJECT_0 + // after this loop + result = TW_TIMEOUT; + break; + } + else + { + timeOut -= deltaTime; + } + } + else + { + result = TW_FAILED; + break; + } + } + + if (numEvent >= nCount) + { + result = WAIT_OBJECT_0; + } + } + else // Wait for one event to be set + { + // no event fired yet, wait on semaphore + res = sys_semaphore_wait( gWaitObjectsSemaphore, timeOut == TT_INFINITE ? 0 : timeOut ); + + if (res == ETIMEDOUT) + { + result = TW_TIMEOUT; + } + else if (res == CELL_OK) + { + if ((event < 0) || (event >= nCount)) + { + DEBUG_ERROR("Bad event\n"); + } + + result = WAIT_OBJECT_0 + event; + } + } + + // run through events unregistering this thread, for benefit + // of those events that did not fire, or fired before semaphore + // was registered + for (int i = 0; i < nCount; i++) + { + lppHandles[i]->UnregisterWaitingThread(&gWaitObjectsSemaphore); + } + + // reset semaphore + while (sys_semaphore_trywait(gWaitObjectsSemaphore) != EBUSY); + + return result; + + + ////////////////////////////////////////////////////////////// +#endif // NEW_WAIT_FOR_MULTIPLE_OBJECTS + ////////////////////////////////////////////////////////////// +} + +#endif diff --git a/external/vpc/tier0/tier0_strtools.cpp b/external/vpc/tier0/tier0_strtools.cpp new file mode 100644 index 0000000..a651f26 --- /dev/null +++ b/external/vpc/tier0/tier0_strtools.cpp @@ -0,0 +1,53 @@ +//========= Copyright � Valve Corporation, All rights reserved. ============// +#include "pch_tier0.h" +#include "tier0_strtools.h" + + +#define TOLOWERC( x ) (( ( x >= 'A' ) && ( x <= 'Z' ) )?( x + 32 ) : x ) +extern "C" +int V_tier0_stricmp(const char *s1, const char *s2 ) +{ + uint8 const *pS1 = ( uint8 const * ) s1; + uint8 const *pS2 = ( uint8 const * ) s2; + for(;;) + { + int c1 = *( pS1++ ); + int c2 = *( pS2++ ); + if ( c1 == c2 ) + { + if ( !c1 ) return 0; + } + else + { + if ( ! c2 ) + { + return c1 - c2; + } + c1 = TOLOWERC( c1 ); + c2 = TOLOWERC( c2 ); + if ( c1 != c2 ) + { + return c1 - c2; + } + } + c1 = *( pS1++ ); + c2 = *( pS2++ ); + if ( c1 == c2 ) + { + if ( !c1 ) return 0; + } + else + { + if ( ! c2 ) + { + return c1 - c2; + } + c1 = TOLOWERC( c1 ); + c2 = TOLOWERC( c2 ); + if ( c1 != c2 ) + { + return c1 - c2; + } + } + } +} diff --git a/external/vpc/tier0/tier0_strtools.h b/external/vpc/tier0/tier0_strtools.h new file mode 100644 index 0000000..d65e789 --- /dev/null +++ b/external/vpc/tier0/tier0_strtools.h @@ -0,0 +1,3 @@ +//========= Copyright � Valve Corporation, All rights reserved. ============// + +extern "C" int V_tier0_stricmp(const char *s1, const char *s2 );
\ No newline at end of file diff --git a/external/vpc/tier0/valobject.cpp b/external/vpc/tier0/valobject.cpp new file mode 100644 index 0000000..caf4283 --- /dev/null +++ b/external/vpc/tier0/valobject.cpp @@ -0,0 +1,124 @@ +//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "pch_tier0.h" +#include "vstdlib/pch_vstdlib.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +#ifdef DBGFLAG_VALIDATE + +//----------------------------------------------------------------------------- +// Purpose: Initializer +// Input: pchType - Type of the object we represent. +// WARNING: pchType must be a static (since we keep a copy of it around for a while) +// pvObj - Pointer to the object we represent +// pchName - Name of the individual object we represent +// WARNING: pchName must be a static (since we keep a copy of it around for a while) +// pValObjectparent- Our parent object (ie, the object that our object is a member of) +// pValObjectPrev - Object that precedes us in the linked list (we're +// always added to the end) +//----------------------------------------------------------------------------- +void CValObject::Init( tchar *pchType, void *pvObj, tchar *pchName, + CValObject *pValObjectParent, CValObject *pValObjectPrev ) +{ + m_nUser = 0; + + // Initialize pchType: + if ( NULL != pchType ) + { + V_strncpy( m_rgchType, pchType, (int) ( sizeof(m_rgchType) / sizeof(*m_rgchType) ) ); + } + else + { + m_rgchType[0] = '\0'; + } + + m_pvObj = pvObj; + + // Initialize pchName: + if ( NULL != pchName ) + { + V_strncpy( m_rgchName, pchName, sizeof(m_rgchName) / sizeof(*m_rgchName) ); + } + else + { + m_rgchName[0] = NULL; + } + + m_pValObjectParent = pValObjectParent; + + if ( NULL == pValObjectParent ) + m_nLevel = 0; + else + m_nLevel = pValObjectParent->NLevel( ) + 1; + + m_cpubMemSelf = 0; + m_cubMemSelf = 0; + m_cpubMemTree = 0; + m_cubMemTree = 0; + + // Insert us at the back of the linked list + if ( NULL != pValObjectPrev ) + { + Assert( NULL == pValObjectPrev->m_pValObjectNext ); + pValObjectPrev->m_pValObjectNext = this; + } + m_pValObjectNext = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CValObject::~CValObject( ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: The object we represent has claimed direct ownership of a block of +// memory. Record that we own it. +// Input: pvMem - Address of the memory block +//----------------------------------------------------------------------------- +void CValObject::ClaimMemoryBlock( void *pvMem ) +{ + // Get the memory block header + CMemBlockHdr *pMemBlockHdr = CMemBlockHdr::PMemBlockHdrFromPvUser( pvMem ); + pMemBlockHdr->CheckValid( ); + + // Update our counters + m_cpubMemSelf++; + m_cubMemSelf+= pMemBlockHdr->CubUser( ); + m_cpubMemTree++; + m_cubMemTree+= pMemBlockHdr->CubUser( ); + + // If we have a parent object, let it know about the memory (it'll recursively call up the tree) + if ( NULL != m_pValObjectParent ) + m_pValObjectParent->ClaimChildMemoryBlock( pMemBlockHdr->CubUser( ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: A child of ours has claimed ownership of a memory block. Make +// a note of it, and pass the message back up the tree. +// Input: cubUser - Size of the memory block +//----------------------------------------------------------------------------- +void CValObject::ClaimChildMemoryBlock( int cubUser ) +{ + m_cpubMemTree++; + m_cubMemTree += cubUser; + + if ( NULL != m_pValObjectParent ) + m_pValObjectParent->ClaimChildMemoryBlock( cubUser ); +} + + + +#endif // DBGFLAG_VALIDATE
\ No newline at end of file diff --git a/external/vpc/tier0/vprof.cpp b/external/vpc/tier0/vprof.cpp new file mode 100644 index 0000000..cf6d7ba --- /dev/null +++ b/external/vpc/tier0/vprof.cpp @@ -0,0 +1,1752 @@ +//===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: Real-Time Hierarchical Profiling +// +// $NoKeywords: $ +//===========================================================================// + +#include "pch_tier0.h" + +#include "tier0/memalloc.h" +#include "tier0/valve_off.h" + +#if defined(_WIN32) && !defined(_X360) +#define WIN_32_LEAN_AND_MEAN +#include <windows.h> +#endif + +#include <assert.h> + +#ifdef _WIN32 +#pragma warning(disable:4073) +#pragma init_seg( lib ) +#endif + +#pragma warning(push, 1) +#pragma warning(disable:4786) +#pragma warning(disable:4530) +#include <map> +#include <vector> +#include <algorithm> +#pragma warning(pop) + +#include "tier0/valve_on.h" +#include "tier0/vprof.h" +#include "tier0/l2cache.h" +#include "strtools.h" + + +#ifdef _X360 + +#include "xbox/xbox_console.h" + +#elif defined(_PS3) +#include "ps3/ps3_console.h" + +#else // NOT _X360: + +#include "tier0/memdbgon.h" + +#endif + +// NOTE: Explicitly and intentionally using STL in here to not generate any +// cyclical dependencies between the low-level debug library and the higher +// level data structures (toml 01-27-03) +using namespace std; + +#ifdef VPROF_ENABLED + + +#if defined(_X360) && !defined(_CERT) // enable PIX CPU trace: +#include "tracerecording.h" +#pragma comment( lib, "tracerecording.lib" ) +#pragma comment( lib, "xbdm.lib" ) +#endif + +//----------------------------------------------------------------------------- +bool g_VProfSignalSpike; + +//----------------------------------------------------------------------------- + +CVProfile g_VProfCurrentProfile; + +int CVProfNode::s_iCurrentUniqueNodeID = 0; + + + +CVProfNode::~CVProfNode() +{ +#if !defined( _WIN32 ) && !defined( POSIX ) + delete m_pChild; + delete m_pSibling; +#endif +} + +CVProfNode *CVProfNode::GetSubNode( const tchar *pszName, int detailLevel, const tchar *pBudgetGroupName, int budgetFlags ) +{ + // Try to find this sub node + CVProfNode * child = m_pChild; + while ( child ) + { + if ( child->m_pszName == pszName ) + { + return child; + } + child = child->m_pSibling; + } + + // We didn't find it, so add it + CVProfNode * node = new CVProfNode( pszName, detailLevel, this, pBudgetGroupName, budgetFlags ); + node->m_pSibling = m_pChild; + m_pChild = node; + return node; +} + +CVProfNode *CVProfNode::GetSubNode( const tchar *pszName, int detailLevel, const tchar *pBudgetGroupName ) +{ + return GetSubNode( pszName, detailLevel, pBudgetGroupName, BUDGETFLAG_OTHER ); +} + + +//------------------------------------- + +void CVProfNode::EnterScope() +{ + m_nCurFrameCalls++; + if ( m_nRecursions++ == 0 ) + { + m_Timer.Start(); +#ifndef _X360 + if ( g_VProfCurrentProfile.UsePME() ) + { + m_L2Cache.Start(); + } +#else // 360 code: + if ( g_VProfCurrentProfile.UsePME() || ((m_iBitFlags & kRecordL2) != 0) ) + { + m_PMCData.Start(); + } + + if ( (m_iBitFlags & kCPUTrace) != 0) + { + // this node is to be recorded. Which recording mode are we in? + switch ( g_VProfCurrentProfile.GetCPUTraceMode() ) + { + case CVProfile::kFirstHitNode: + case CVProfile::kAllNodesInFrame_Recording: + case CVProfile::kAllNodesInFrame_RecordingMultiFrame: + // we are presently recording. + if ( !XTraceStartRecording( g_VProfCurrentProfile.GetCPUTraceFilename() ) ) + { + Msg( "XTraceStartRecording failed, error code %d\n", GetLastError() ); + } + + default: + // no default. + break; + } + + } + +#endif + +#ifdef VPROF_VTUNE_GROUP + g_VProfCurrentProfile.PushGroup( m_BudgetGroupID ); +#endif + } +} + +//------------------------------------- + +bool CVProfNode::ExitScope() +{ + if ( --m_nRecursions == 0 && m_nCurFrameCalls != 0 ) + { + m_Timer.End(); + m_CurFrameTime += m_Timer.GetDuration(); +#ifndef _X360 + if ( g_VProfCurrentProfile.UsePME() ) + { + m_L2Cache.End(); + m_iCurL2CacheMiss += m_L2Cache.GetL2CacheMisses(); + } +#else // 360 code: + if ( g_VProfCurrentProfile.UsePME() || ((m_iBitFlags & kRecordL2) != 0) ) + { + m_PMCData.End(); + m_iCurL2CacheMiss += m_PMCData.GetL2CacheMisses(); + m_iCurLoadHitStores += m_PMCData.GetLHS(); + } + + if ( (m_iBitFlags & kCPUTrace) != 0 ) + { + // this node is enabled to be recorded. What mode are we in? + switch ( g_VProfCurrentProfile.GetCPUTraceMode() ) + { + case CVProfile::kFirstHitNode: + { + // one-off recording. stop now. + if ( XTraceStopRecording() ) + { + Msg( "CPU trace finished.\n" ); + if ( g_VProfCurrentProfile.TraceCompleteEvent() ) + { + // signal VXConsole that trace is completed + XBX_rTraceComplete(); + } + } + // don't trace again next frame, overwriting the file. + g_VProfCurrentProfile.SetCPUTraceEnabled( CVProfile::kDisabled ); + break; + } + + case CVProfile::kAllNodesInFrame_Recording: + case CVProfile::kAllNodesInFrame_RecordingMultiFrame: + { + // one-off recording. stop now. + if ( XTraceStopRecording() ) + { + if ( g_VProfCurrentProfile.GetCPUTraceMode() == CVProfile::kAllNodesInFrame_RecordingMultiFrame ) + { + Msg( "%.3f msec in %s\n", m_CurFrameTime.GetMillisecondsF(), g_VProfCurrentProfile.GetCPUTraceFilename() ); + } + else + { + Msg( "CPU trace finished.\n" ); + } + } + + // Spew time info for file to allow figuring it out later + g_VProfCurrentProfile.LatchMultiFrame( m_CurFrameTime.GetLongCycles() ); + +#if 0 // This doesn't want to work on the xbox360-- MoveFile not available or file still being put down to disk? + char suffix[ 32 ]; + _snprintf( suffix, sizeof( suffix ), "_%.3f_msecs", flMsecs ); + + char fn[ 512 ]; + + strncpy( fn, g_VProfCurrentProfile.GetCPUTraceFilename(), sizeof( fn ) ); + + char *p = strrchr( fn, '.' ); + if ( *p ) + { + *p = 0; + } + strncat( fn, suffix, sizeof( fn ) ); + strncat( fn, ".pix2", sizeof( fn ) ); + + BOOL bSuccess = MoveFile( g_VProfCurrentProfile.GetCPUTraceFilename(), fn ); + if ( !bSuccess ) + { + DWORD eCode = GetLastError(); + Msg( "Error %d\n", eCode ); + } +#endif + + // we're still recording until the frame is done. + // but, increment the index. + g_VProfCurrentProfile.IncrementMultiTraceIndex(); + break; + } + + } + + // g_VProfCurrentProfile.IsCPUTraceEnabled() && + + + } + +#endif + +#ifdef VPROF_VTUNE_GROUP + g_VProfCurrentProfile.PopGroup(); +#endif + } + return ( m_nRecursions == 0 ); +} + +//------------------------------------- + +void CVProfNode::Pause() +{ + if ( m_nRecursions > 0 ) + { + m_Timer.End(); + m_CurFrameTime += m_Timer.GetDuration(); + +#ifndef _X360 + if ( g_VProfCurrentProfile.UsePME() ) + { + m_L2Cache.End(); + m_iCurL2CacheMiss += m_L2Cache.GetL2CacheMisses(); + } +#else // 360 code: + if ( g_VProfCurrentProfile.UsePME() || ((m_iBitFlags & kRecordL2) != 0) ) + { + m_PMCData.End(); + m_iCurL2CacheMiss += m_PMCData.GetL2CacheMisses(); + m_iCurLoadHitStores += m_PMCData.GetLHS(); + } +#endif + } + if ( m_pChild ) + { + m_pChild->Pause(); + } + if ( m_pSibling ) + { + m_pSibling->Pause(); + } +} + +//------------------------------------- + +void CVProfNode::Resume() +{ + if ( m_nRecursions > 0 ) + { + m_Timer.Start(); + +#ifndef _X360 + if ( g_VProfCurrentProfile.UsePME() ) + { + m_L2Cache.Start(); + } +#else + if ( g_VProfCurrentProfile.UsePME() || ((m_iBitFlags & kRecordL2) != 0) ) + { + m_PMCData.Start(); + } +#endif + } + if ( m_pChild ) + { + m_pChild->Resume(); + } + if ( m_pSibling ) + { + m_pSibling->Resume(); + } +} + +//------------------------------------- + +void CVProfNode::Reset() +{ + m_nPrevFrameCalls = 0; + m_PrevFrameTime.Init(); + + m_nCurFrameCalls = 0; + m_CurFrameTime.Init(); + + m_nTotalCalls = 0; + m_TotalTime.Init(); + + m_PeakTime.Init(); + + m_iPrevL2CacheMiss = 0; + m_iCurL2CacheMiss = 0; + m_iTotalL2CacheMiss = 0; + +#ifdef _X360 + m_iPrevLoadHitStores = 0; + m_iCurLoadHitStores = 0; + m_iTotalLoadHitStores = 0; +#endif + + if ( m_pChild ) + { + m_pChild->Reset(); + } + if ( m_pSibling ) + { + m_pSibling->Reset(); + } +} + + +//------------------------------------- + +void CVProfNode::MarkFrame() +{ + m_nPrevFrameCalls = m_nCurFrameCalls; + m_PrevFrameTime = m_CurFrameTime; + m_iPrevL2CacheMiss = m_iCurL2CacheMiss; +#ifdef _X360 + m_iPrevLoadHitStores = m_iCurLoadHitStores; +#endif + m_nTotalCalls += m_nCurFrameCalls; + m_TotalTime += m_CurFrameTime; + + if ( m_PeakTime.IsLessThan( m_CurFrameTime ) ) + { + m_PeakTime = m_CurFrameTime; + } + + m_CurFrameTime.Init(); + m_nCurFrameCalls = 0; + m_iTotalL2CacheMiss += m_iCurL2CacheMiss; + m_iCurL2CacheMiss = 0; +#ifdef _X360 + m_iTotalLoadHitStores += m_iCurLoadHitStores; + m_iCurLoadHitStores = 0; +#endif + if ( m_pChild ) + { + m_pChild->MarkFrame(); + } + if ( m_pSibling ) + { + m_pSibling->MarkFrame(); + } +} + +//------------------------------------- + +void CVProfNode::ResetPeak() +{ + m_PeakTime.Init(); + + if ( m_pChild ) + { + m_pChild->ResetPeak(); + } + if ( m_pSibling ) + { + m_pSibling->ResetPeak(); + } +} + + +void CVProfNode::SetCurFrameTime( unsigned long milliseconds ) +{ + m_CurFrameTime.Init( (float)milliseconds ); +} +#ifdef DBGFLAG_VALIDATE +//----------------------------------------------------------------------------- +// Purpose: Ensure that all of our internal structures are consistent, and +// account for all memory that we've allocated. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void CVProfNode::Validate( CValidator &validator, tchar *pchName ) +{ + validator.Push( _T("CVProfNode"), this, pchName ); + + m_L2Cache.Validate( validator, _T("m_L2Cache") ); + + if ( m_pSibling ) + m_pSibling->Validate( validator, _T("m_pSibling") ); + if ( m_pChild ) + m_pChild->Validate( validator, _T("m_pChild") ); + + validator.Pop( ); +} +#endif // DBGFLAG_VALIDATE + + + +//----------------------------------------------------------------------------- + +struct TimeSums_t +{ + const tchar *pszProfileScope; + unsigned calls; + double time; + double timeLessChildren; + double peak; +}; + +static bool TimeCompare( const TimeSums_t &lhs, const TimeSums_t &rhs ) +{ + return ( lhs.time > rhs.time ); +} + +static bool TimeLessChildrenCompare( const TimeSums_t &lhs, const TimeSums_t &rhs ) +{ + return ( lhs.timeLessChildren > rhs.timeLessChildren ); +} + +static bool PeakCompare( const TimeSums_t &lhs, const TimeSums_t &rhs ) +{ + return ( lhs.peak > rhs.peak ); +} + +static bool AverageTimeCompare( const TimeSums_t &lhs, const TimeSums_t &rhs ) +{ + double avgLhs = ( lhs.calls ) ? lhs.time / (double)lhs.calls : 0.0; + double avgRhs = ( rhs.calls ) ? rhs.time / (double)rhs.calls : 0.0; + return ( avgLhs > avgRhs ); +} + +static bool AverageTimeLessChildrenCompare( const TimeSums_t &lhs, const TimeSums_t &rhs ) +{ + double avgLhs = ( lhs.calls ) ? lhs.timeLessChildren / (double)lhs.calls : 0.0; + double avgRhs = ( rhs.calls ) ? rhs.timeLessChildren / (double)rhs.calls : 0.0; + return ( avgLhs > avgRhs ); + +} +static bool PeakOverAverageCompare( const TimeSums_t &lhs, const TimeSums_t &rhs ) +{ + double avgLhs = ( lhs.calls ) ? lhs.time / (double)lhs.calls : 0.0; + double avgRhs = ( rhs.calls ) ? rhs.time / (double)rhs.calls : 0.0; + + double lhsPoA = ( avgLhs != 0 ) ? lhs.peak / avgLhs : 0.0; + double rhsPoA = ( avgRhs != 0 ) ? rhs.peak / avgRhs : 0.0; + + return ( lhsPoA > rhsPoA ); +} + +map<CVProfNode *, double> g_TimesLessChildren; +int g_TotalFrames; +map<const tchar *, uintp> g_TimeSumsMap; +vector<TimeSums_t> g_TimeSums; +CVProfNode * g_pStartNode; +const tchar * g_pszSumNode; + +//------------------------------------- + +void CVProfile::SumTimes( CVProfNode *pNode, int budgetGroupID ) +{ + if ( !pNode ) + return; // this generally only happens on a failed FindNode() + + bool bSetStartNode; + if ( !g_pStartNode && _tcscmp( pNode->GetName(), g_pszSumNode ) == 0 ) + { + g_pStartNode = pNode; + bSetStartNode = true; + } + else + bSetStartNode = false; + + if ( GetRoot() != pNode ) + { + if ( g_pStartNode && pNode->GetTotalCalls() > 0 && ( budgetGroupID == -1 || pNode->GetBudgetGroupID() == budgetGroupID ) ) + { + double timeLessChildren = pNode->GetTotalTimeLessChildren(); + + g_TimesLessChildren.insert( make_pair( pNode, timeLessChildren ) ); + + map<const tchar *, uintp>::iterator iter; + iter = g_TimeSumsMap.find( pNode->GetName() ); // intenionally using address of string rather than string compare (toml 01-27-03) + if ( iter == g_TimeSumsMap.end() ) + { + TimeSums_t timeSums = { pNode->GetName(), pNode->GetTotalCalls(), pNode->GetTotalTime(), timeLessChildren, pNode->GetPeakTime() }; + g_TimeSumsMap.insert( make_pair( pNode->GetName(), g_TimeSums.size() ) ); + g_TimeSums.push_back( timeSums ); + } + else + { + TimeSums_t &timeSums = g_TimeSums[iter->second]; + timeSums.calls += pNode->GetTotalCalls(); + timeSums.time += pNode->GetTotalTime(); + timeSums.timeLessChildren += timeLessChildren; + if ( pNode->GetPeakTime() > timeSums.peak ) + timeSums.peak = pNode->GetPeakTime(); + } + } + + if( ( !g_pStartNode || pNode != g_pStartNode ) && pNode->GetSibling() ) + { + SumTimes( pNode->GetSibling(), budgetGroupID ); + } + } + + if( pNode->GetChild() ) + { + SumTimes( pNode->GetChild(), budgetGroupID ); + } + + if ( bSetStartNode ) + g_pStartNode = NULL; +} + +//------------------------------------- + +CVProfNode *CVProfile::FindNode( CVProfNode *pStartNode, const tchar *pszNode ) +{ + if ( _tcscmp( pStartNode->GetName(), pszNode ) != 0 ) + { + CVProfNode *pFoundNode = NULL; + if ( pStartNode->GetSibling() ) + { + pFoundNode = FindNode( pStartNode->GetSibling(), pszNode ); + } + + if ( !pFoundNode && pStartNode->GetChild() ) + { + pFoundNode = FindNode( pStartNode->GetChild(), pszNode ); + } + + return pFoundNode; + } + return pStartNode; +} + +//------------------------------------- +#ifdef _X360 + +void CVProfile::PMCDisableAllNodes(CVProfNode *pStartNode) +{ + if (pStartNode == NULL) + { + pStartNode = GetRoot(); + } + + pStartNode->EnableL2andLHS(false); + + if ( pStartNode->GetSibling() ) + { + PMCDisableAllNodes(pStartNode->GetSibling()); + } + + if ( pStartNode->GetChild() ) + { + PMCDisableAllNodes(pStartNode->GetChild()); + } +} + +// recursively set l2/lhs recording state for a node and all children AND SIBLINGS +static void PMCRecursiveL2Set(CVProfNode *pNode, bool enableState) +{ + if ( pNode ) + { + pNode->EnableL2andLHS(enableState); + if ( pNode->GetSibling() ) + { + PMCRecursiveL2Set( pNode->GetSibling(), enableState ); + } + if ( pNode->GetChild() ) + { + PMCRecursiveL2Set( pNode->GetChild(), enableState ); + } + } +} + +bool CVProfile::PMCEnableL2Upon(const tchar *pszNodeName, bool bRecursive) +{ + // PMCDisableAllNodes(); + CVProfNode *pNode = FindNode( GetRoot(), pszNodeName ); + if (pNode) + { + pNode->EnableL2andLHS(true); + if (bRecursive) + { + PMCRecursiveL2Set(pNode->GetChild(), true); + } + return true; + } + else + { + return false; + } +} + +bool CVProfile::PMCDisableL2Upon(const tchar *pszNodeName, bool bRecursive) +{ + // PMCDisableAllNodes(); + CVProfNode *pNode = FindNode( GetRoot(), pszNodeName ); + if ( pNode ) + { + pNode->EnableL2andLHS( false ); + if ( bRecursive ) + { + PMCRecursiveL2Set( pNode->GetChild(), false ); + } + return true; + } + else + { + return false; + } +} + +static void DumpEnabledPMCNodesInner(CVProfNode* pNode) +{ + if (!pNode) + return; + + if (pNode->IsL2andLHSEnabled()) + { + Msg( _T("\t%s\n"), pNode->GetName() ); + } + + // depth first printing clearer + if ( pNode->GetChild() ) + { + DumpEnabledPMCNodesInner(pNode->GetChild()); + } + + if ( pNode->GetSibling() ) + { + DumpEnabledPMCNodesInner(pNode->GetChild()); + } +} + +void CVProfile::DumpEnabledPMCNodes( void ) +{ + Msg( _T("Nodes enabled for PMC counters:\n") ); + CVProfNode *pNode = GetRoot(); + DumpEnabledPMCNodesInner( pNode ); + + Msg( _T("(end)\n") ); +} + +CVProfNode *CVProfile::CPUTraceGetEnabledNode(CVProfNode *pStartNode) +{ + if (!pStartNode) + { + pStartNode = GetRoot(); + } + + if ( (pStartNode->m_iBitFlags & CVProfNode::kCPUTrace) != 0 ) + { + return pStartNode; + } + + if (pStartNode->GetSibling()) + { + CVProfNode *retval = CPUTraceGetEnabledNode(pStartNode->GetSibling()); + if (retval) + return retval; + } + + if (pStartNode->GetChild()) + { + CVProfNode *retval = CPUTraceGetEnabledNode(pStartNode->GetChild()); + if (retval) + return retval; + } + + return NULL; +} + +const char *CVProfile::SetCPUTraceFilename( const char *filename ) +{ + strncpy( m_CPUTraceFilename, filename, sizeof( m_CPUTraceFilename ) ); + return GetCPUTraceFilename(); +} + +/// Returns a pointer to an internal static, so you don't need to +/// make temporary char buffers for this to write into. What of it? +/// You're not hanging on to that pointer. That would be foolish. +const char *CVProfile::GetCPUTraceFilename() +{ + static char retBuf[256]; + + switch ( m_iCPUTraceEnabled ) + { + case kAllNodesInFrame_WaitingForMark: + case kAllNodesInFrame_Recording: + _snprintf( retBuf, sizeof( retBuf ), "e:\\%.128s%.4d.pix2", m_CPUTraceFilename, m_iSuccessiveTraceIndex ); + break; + + case kAllNodesInFrame_WaitingForMarkMultiFrame: + case kAllNodesInFrame_RecordingMultiFrame: + _snprintf( retBuf, sizeof( retBuf ), "e:\\%.128s_%.4d_%.4d.pix2", m_CPUTraceFilename, m_nFrameCount, m_iSuccessiveTraceIndex ); + break; + + default: + _snprintf( retBuf, sizeof( retBuf ), "e:\\%.128s.pix2", m_CPUTraceFilename ); + } + + return retBuf; +} + +bool CVProfile::TraceCompleteEvent( void ) +{ + return m_bTraceCompleteEvent; +} + +CVProfNode *CVProfile::CPUTraceEnableForNode(const tchar *pszNodeName) +{ + // disable whatever may be enabled already (we can only trace one node at a time) + CPUTraceDisableAllNodes(); + + CVProfNode *which = FindNode(GetRoot(), pszNodeName); + if (which) + { + which->m_iBitFlags |= CVProfNode::kCPUTrace; + return which; + } + else + return NULL; +} + +void CVProfile::CPUTraceDisableAllNodes(CVProfNode *pStartNode) +{ + if (!pStartNode) + { + pStartNode = GetRoot(); + } + + pStartNode->m_iBitFlags &= ~CVProfNode::kCPUTrace; + + if (pStartNode->GetSibling()) + { + CPUTraceDisableAllNodes(pStartNode->GetSibling()); + } + + if (pStartNode->GetChild()) + { + CPUTraceDisableAllNodes(pStartNode->GetChild()); + } + +} + +#endif + +//------------------------------------- + +void CVProfile::SumTimes( const tchar *pszStartNode, int budgetGroupID ) +{ + if ( GetRoot()->GetChild() ) + { + if ( pszStartNode == NULL ) + g_pStartNode = GetRoot(); + else + g_pStartNode = NULL; + + g_pszSumNode = pszStartNode; + SumTimes( GetRoot(), budgetGroupID ); + g_pStartNode = NULL; + } + +} + +//------------------------------------- + +void CVProfile::DumpNodes( CVProfNode *pNode, int indent, bool bAverageAndCountOnly ) +{ + if ( !pNode ) + return; // this generally only happens on a failed FindNode() + + bool fIsRoot = ( pNode == GetRoot() ); + + if ( fIsRoot || pNode == g_pStartNode ) + { + if( bAverageAndCountOnly ) + { + Msg( _T(" Avg Time/Frame (ms)\n") ); + Msg( _T("[ func+child func ] Count\n") ); + Msg( _T(" ---------- --------- --------\n") ); + } + else + { + Msg( _T(" Sum (ms) Avg Time/Frame (ms) Avg Time/Call (ms)\n") ); + Msg( _T("[ func+child func ] [ func+child func ] [ func+child func ] Count Peak\n") ); + Msg( _T(" ---------- --------- ---------- ------ ---------- ------ -------- ------\n") ); + } + } + + if ( !fIsRoot ) + { + map<CVProfNode *, double>::iterator iterTimeLessChildren = g_TimesLessChildren.find( pNode ); + + double dNodeTime = 0; + if(iterTimeLessChildren != g_TimesLessChildren.end()) + dNodeTime = iterTimeLessChildren->second; + + if( bAverageAndCountOnly ) + { + Msg( _T(" %10.3f %9.2f %8d"), + ( pNode->GetTotalCalls() > 0 ) ? pNode->GetTotalTime() / (double)NumFramesSampled() : 0, + ( pNode->GetTotalCalls() > 0 ) ? dNodeTime / (double)NumFramesSampled() : 0, + pNode->GetTotalCalls() ); + } + else + { + Msg( _T(" %10.3f %9.2f %10.3f %6.2f %10.3f %6.2f %8d %6.2f"), + pNode->GetTotalTime(), dNodeTime, + ( pNode->GetTotalCalls() > 0 ) ? pNode->GetTotalTime() / (double)NumFramesSampled() : 0, + ( pNode->GetTotalCalls() > 0 ) ? dNodeTime / (double)NumFramesSampled() : 0, + ( pNode->GetTotalCalls() > 0 ) ? pNode->GetTotalTime() / (double)pNode->GetTotalCalls() : 0, + ( pNode->GetTotalCalls() > 0 ) ? dNodeTime / (double)pNode->GetTotalCalls() : 0, + pNode->GetTotalCalls(), pNode->GetPeakTime() ); + } + + Msg( _T(" ") ); + for ( int i = 1; i < indent; i++ ) + { + Msg( _T("| ") ); + } + + Msg( _T("%s\n"), pNode->GetName() ); + } + + if( pNode->GetChild() ) + { + DumpNodes( pNode->GetChild(), indent + 1, bAverageAndCountOnly ); + } + + if( !( fIsRoot || pNode == g_pStartNode ) && pNode->GetSibling() ) + { + DumpNodes( pNode->GetSibling(), indent, bAverageAndCountOnly ); + } +} + +//------------------------------------- + +#if defined( VPROF_VXCONSOLE_EXISTS ) +static void CalcBudgetGroupTimes_Recursive( CVProfNode *pNode, unsigned int *groupTimes, int numGroups, float flScale ) +{ + int groupID; + CVProfNode *nodePtr; + + groupID = pNode->GetBudgetGroupID(); + if ( groupID >= numGroups ) + { + return; + } + + groupTimes[groupID] += flScale*pNode->GetPrevTimeLessChildren(); + + nodePtr = pNode->GetSibling(); + if ( nodePtr ) + { + CalcBudgetGroupTimes_Recursive( nodePtr, groupTimes, numGroups, flScale ); + } + + nodePtr = pNode->GetChild(); + if ( nodePtr ) + { + CalcBudgetGroupTimes_Recursive( nodePtr, groupTimes, numGroups, flScale ); + } +} + +static void CalcBudgetGroupL2CacheMisses_Recursive( CVProfNode *pNode, unsigned int *groupTimes, int numGroups, float flScale ) +{ + int groupID; + CVProfNode *nodePtr; + + groupID = pNode->GetBudgetGroupID(); + if ( groupID >= numGroups ) + { + return; + } + + groupTimes[groupID] += flScale*pNode->GetPrevL2CacheMissLessChildren(); + + nodePtr = pNode->GetSibling(); + if ( nodePtr ) + { + CalcBudgetGroupL2CacheMisses_Recursive( nodePtr, groupTimes, numGroups, flScale ); + } + + nodePtr = pNode->GetChild(); + if ( nodePtr ) + { + CalcBudgetGroupL2CacheMisses_Recursive( nodePtr, groupTimes, numGroups, flScale ); + } +} + +static void CalcBudgetGroupLHS_Recursive( CVProfNode *pNode, unsigned int *groupTimes, int numGroups, float flScale ) +{ + int groupID; + CVProfNode *nodePtr; + + groupID = pNode->GetBudgetGroupID(); + if ( groupID >= numGroups ) + { + return; + } + + groupTimes[groupID] += flScale*pNode->GetPrevLoadHitStoreLessChildren(); + + nodePtr = pNode->GetSibling(); + if ( nodePtr ) + { + CalcBudgetGroupLHS_Recursive( nodePtr, groupTimes, numGroups, flScale ); + } + + nodePtr = pNode->GetChild(); + if ( nodePtr ) + { + CalcBudgetGroupLHS_Recursive( nodePtr, groupTimes, numGroups, flScale ); + } +} + + +void CVProfile::VXConsoleReportMode( VXConsoleReportMode_t mode ) +{ + m_ReportMode = mode; +} + +void CVProfile::VXConsoleReportScale( VXConsoleReportMode_t mode, float flScale ) +{ + m_pReportScale[mode] = flScale; +} + + +//----------------------------------------------------------------------------- +// Send the all the counter attributes once to VXConsole at profiling start +//----------------------------------------------------------------------------- +void CVProfile::VXProfileStart() +{ + const char *names[XBX_MAX_PROFILE_COUNTERS]; + COLORREF colors[XBX_MAX_PROFILE_COUNTERS]; + int numGroups; + int counterGroup; + const char *pGroupName; + int i; + int r,g,b,a; + + // vprof system must be running + if ( m_enabled <= 0 || !m_UpdateMode ) + { + return; + } + + if ( m_UpdateMode & VPROF_UPDATE_BUDGET ) + { + // update budget profiling + numGroups = g_VProfCurrentProfile.GetNumBudgetGroups(); + if ( numGroups > XBX_MAX_PROFILE_COUNTERS ) + { + numGroups = XBX_MAX_PROFILE_COUNTERS; + } + for ( i=0; i<numGroups; i++ ) + { + names[i] = g_VProfCurrentProfile.GetBudgetGroupName( i ); + g_VProfCurrentProfile.GetBudgetGroupColor( i, r, g, b, a ); + colors[i] = XMAKECOLOR( r, g, b ); + } + + // send all the profile attributes + XBX_rSetProfileAttributes( "cpu", numGroups, names, colors ); + } + + if ( m_UpdateMode & (VPROF_UPDATE_TEXTURE_GLOBAL|VPROF_UPDATE_TEXTURE_PERFRAME) ) + { + // update texture profiling + numGroups = 0; + counterGroup = (m_UpdateMode & VPROF_UPDATE_TEXTURE_GLOBAL) ? COUNTER_GROUP_TEXTURE_GLOBAL : COUNTER_GROUP_TEXTURE_PER_FRAME; + for ( i=0; i<g_VProfCurrentProfile.GetNumCounters(); i++ ) + { + if ( g_VProfCurrentProfile.GetCounterGroup( i ) == counterGroup ) + { + // strip undesired prefix + pGroupName = g_VProfCurrentProfile.GetCounterName( i ); + if ( !stricmp( pGroupName, "texgroup_frame_" ) ) + { + pGroupName += 15; + } + else if ( !stricmp( pGroupName, "texgroup_global_" ) ) + { + pGroupName += 16; + } + names[numGroups] = pGroupName; + + g_VProfCurrentProfile.GetBudgetGroupColor( numGroups, r, g, b, a ); + colors[numGroups] = XMAKECOLOR( r, g, b ); + + numGroups++; + if ( numGroups == XBX_MAX_PROFILE_COUNTERS ) + { + break; + } + } + } + + // send all the profile attributes + XBX_rSetProfileAttributes( "texture", numGroups, names, colors ); + } +} + +//----------------------------------------------------------------------------- +// Send the counters to VXConsole +//----------------------------------------------------------------------------- +void CVProfile::VXProfileUpdate() +{ + int i; + int counterGroup; + int numGroups; + unsigned int groupData[XBX_MAX_PROFILE_COUNTERS]; + + // vprof system must be running + if ( m_enabled <= 0 || !m_UpdateMode ) + { + return; + } + + if ( m_UpdateMode & VPROF_UPDATE_BUDGET ) + { + // send the cpu counters + numGroups = g_VProfCurrentProfile.GetNumBudgetGroups(); + if ( numGroups > XBX_MAX_PROFILE_COUNTERS ) + { + numGroups = XBX_MAX_PROFILE_COUNTERS; + } + memset( groupData, 0, numGroups * sizeof( unsigned int ) ); + + CVProfNode *pNode = g_VProfCurrentProfile.GetRoot(); + if ( pNode && pNode->GetChild() ) + { + switch ( m_ReportMode ) + { + default: + case VXCONSOLE_REPORT_TIME: + CalcBudgetGroupTimes_Recursive( pNode->GetChild(), groupData, numGroups, m_pReportScale[VXCONSOLE_REPORT_TIME] ); + break; + + case VXCONSOLE_REPORT_L2CACHE_MISSES: + CalcBudgetGroupL2CacheMisses_Recursive( pNode->GetChild(), groupData, numGroups, m_pReportScale[VXCONSOLE_REPORT_L2CACHE_MISSES] ); + break; + + case VXCONSOLE_REPORT_LOAD_HIT_STORE: + CalcBudgetGroupLHS_Recursive( pNode->GetChild(), groupData, numGroups, m_pReportScale[VXCONSOLE_REPORT_LOAD_HIT_STORE] ); + break; + } + } + + XBX_rSetProfileData( "cpu", numGroups, groupData ); + } + + if ( m_UpdateMode & ( VPROF_UPDATE_TEXTURE_GLOBAL|VPROF_UPDATE_TEXTURE_PERFRAME ) ) + { + // send the texture counters + numGroups = 0; + counterGroup = ( m_UpdateMode & VPROF_UPDATE_TEXTURE_GLOBAL ) ? COUNTER_GROUP_TEXTURE_GLOBAL : COUNTER_GROUP_TEXTURE_PER_FRAME; + for ( i = 0; i < g_VProfCurrentProfile.GetNumCounters(); i++ ) + { + if ( g_VProfCurrentProfile.GetCounterGroup( i ) == counterGroup ) + { + // get the size in bytes + groupData[numGroups++] = g_VProfCurrentProfile.GetCounterValue( i ); + if ( numGroups == XBX_MAX_PROFILE_COUNTERS ) + { + break; + } + } + } + + XBX_rSetProfileData( "texture", numGroups, groupData ); + } +} + +void CVProfile::VXEnableUpdateMode( int event, bool bEnable ) +{ + // enable or disable the updating of specified events + if ( bEnable ) + { + m_UpdateMode |= event; + } + else + { + m_UpdateMode &= ~event; + } + + // force a resend of possibly affected attributes + VXProfileStart(); +} + +#define MAX_VPROF_NODES_IN_LIST 4096 +static void VXBuildNodeList_r( CVProfNode *pNode, xVProfNodeItem_t *pNodeList, int *pNumNodes ) +{ + if ( !pNode ) + { + return; + } + if ( *pNumNodes >= MAX_VPROF_NODES_IN_LIST ) + { + // list full + return; + } + + // add to list + pNodeList[*pNumNodes].pName = (const char *)pNode->GetName(); + + pNodeList[*pNumNodes].pBudgetGroupName = g_VProfCurrentProfile.GetBudgetGroupName( pNode->GetBudgetGroupID() ); + int r, g, b, a; + g_VProfCurrentProfile.GetBudgetGroupColor( pNode->GetBudgetGroupID(), r, g, b, a ); + pNodeList[*pNumNodes].budgetGroupColor = XMAKECOLOR( r, g, b ); + + pNodeList[*pNumNodes].totalCalls = pNode->GetTotalCalls(); + pNodeList[*pNumNodes].inclusiveTime = pNode->GetTotalTime(); + pNodeList[*pNumNodes].exclusiveTime = pNode->GetTotalTimeLessChildren(); + (*pNumNodes)++; + + CVProfNode *nodePtr = pNode->GetSibling(); + if ( nodePtr ) + { + VXBuildNodeList_r( nodePtr, pNodeList, pNumNodes ); + } + + nodePtr = pNode->GetChild(); + if ( nodePtr ) + { + VXBuildNodeList_r( nodePtr, pNodeList, pNumNodes ); + } +} +void CVProfile::VXSendNodes( void ) +{ + Pause(); + + xVProfNodeItem_t *pNodeList = (xVProfNodeItem_t *)stackalloc( MAX_VPROF_NODES_IN_LIST * sizeof(xVProfNodeItem_t) ); + int numNodes = 0; + VXBuildNodeList_r( GetRoot(), pNodeList, &numNodes ); + + // send to vxconsole + XBX_rVProfNodeList( numNodes, pNodeList ); + + Resume(); +} +#endif + +//------------------------------------- +static void DumpSorted( const tchar *pszHeading, double totalTime, bool (*pfnSort)( const TimeSums_t &, const TimeSums_t & ), int maxLen = 999999 ) +{ + unsigned i; + vector<TimeSums_t> sortedSums; + sortedSums = g_TimeSums; + sort( sortedSums.begin(), sortedSums.end(), pfnSort ); + + Msg( _T("%s\n"), pszHeading); + Msg( _T(" Scope Calls Calls/Frame Time+Child Pct Time Pct Avg/Frame Avg/Call Avg-NoChild Peak\n")); + Msg( _T(" ---------------------------------------------------- ----------- ----------- ----------- ------ ----------- ------ ----------- ----------- ----------- -----------\n")); + for ( i = 0; i < sortedSums.size() && i < (unsigned)maxLen; i++ ) + { + double avg = ( sortedSums[i].calls ) ? sortedSums[i].time / (double)sortedSums[i].calls : 0.0; + double avgLessChildren = ( sortedSums[i].calls ) ? sortedSums[i].timeLessChildren / (double)sortedSums[i].calls : 0.0; + + Msg( _T(" %52.52s%12d%12.3f%12.3f%7.2f%12.3f%7.2f%12.3f%12.3f%12.3f%12.3f\n"), + sortedSums[i].pszProfileScope, + sortedSums[i].calls, + (float)sortedSums[i].calls / (float)g_TotalFrames, + sortedSums[i].time, + min( ( sortedSums[i].time / totalTime ) * 100.0, 100.0 ), + sortedSums[i].timeLessChildren, + min( ( sortedSums[i].timeLessChildren / totalTime ) * 100.0, 100.0 ), + sortedSums[i].time / (float)g_TotalFrames, + avg, + avgLessChildren, + sortedSums[i].peak ); + } +} + +#if defined( _X360 ) +// Dump information on all nodes with PMC recording +static void DumpPMC( CVProfNode *pNode, bool &bPrintHeader, uint64 L2thresh = 1, uint64 LHSthresh = 1 ) +{ + if (!pNode) return; + + uint64 l2 = pNode->GetL2CacheMisses(); + uint64 lhs = pNode->GetLoadHitStores(); + if ( l2 > L2thresh && + lhs > LHSthresh ) + { + // met threshold. + if (bPrintHeader) + { + // print header + Msg( _T("-- 360 PMC information --\n") ); + Msg( _T("Scope L2/call L2/frame LHS/call LHS/frame\n") ); + Msg( _T("---------------------------------------------------- --------- --------- --------- ---------\n") ); + + bPrintHeader = false; + } + + // print + float calls = pNode->GetTotalCalls(); + float frames = g_TotalFrames; + Msg( _T("%52.52s %9.2f %9.2f %9.2f %9.2f\n"), pNode->GetName(), l2/calls, l2/frames, lhs/calls, lhs/frames ); + } + + if ( pNode->GetSibling() ) + { + DumpPMC( pNode->GetSibling(), bPrintHeader, L2thresh, LHSthresh ); + } + + if ( pNode->GetChild() ) + { + DumpPMC( pNode->GetChild(), bPrintHeader, L2thresh, LHSthresh ); + } +} +#endif + +//------------------------------------- + +void CVProfile::OutputReport( int type, const tchar *pszStartNode, int budgetGroupID ) +{ + Msg( _T("******** BEGIN VPROF REPORT ********\n")); +#ifdef _MSC_VER +#if (_MSC_VER < 1300) + Msg( _T(" (note: this report exceeds the output capacity of MSVC debug window. Use console window or console log.) \n")); +#endif +#endif + + g_TotalFrames = max( NumFramesSampled() - 1, 1 ); + + if ( NumFramesSampled() == 0 || GetTotalTimeSampled() == 0) + Msg( _T("No samples\n") ); + else + { + if ( type & VPRT_SUMMARY ) + { + Msg( _T("-- Summary --\n") ); + Msg( _T("%d frames sampled for %.2f seconds\n"), g_TotalFrames, GetTotalTimeSampled() / 1000.0 ); + Msg( _T("Average %.2f fps, %.2f ms per frame\n"), 1000.0 / ( GetTotalTimeSampled() / g_TotalFrames ), GetTotalTimeSampled() / g_TotalFrames ); + Msg( _T("Peak %.2f ms frame\n"), GetPeakFrameTime() ); + + double timeAccountedFor = 100.0 - ( m_Root.GetTotalTimeLessChildren() / m_Root.GetTotalTime() ); + Msg( _T("%.0f pct of time accounted for\n"), min( 100.0, timeAccountedFor ) ); + Msg( _T("\n") ); + } + + if ( pszStartNode == NULL ) + { + pszStartNode = GetRoot()->GetName(); + } + + SumTimes( pszStartNode, budgetGroupID ); + + // Dump the hierarchy + if ( type & VPRT_HIERARCHY ) + { + Msg( _T("-- Hierarchical Call Graph --\n")); + if ( pszStartNode == NULL ) + g_pStartNode = NULL; + else + g_pStartNode = FindNode( GetRoot(), pszStartNode ); + + DumpNodes( (!g_pStartNode) ? GetRoot() : g_pStartNode, 0, false ); + Msg( _T("\n") ); + } + + if ( type & VPRT_HIERARCHY_TIME_PER_FRAME_AND_COUNT_ONLY ) + { + Msg( _T("-- Hierarchical Call Graph --\n")); + if ( pszStartNode == NULL ) + g_pStartNode = NULL; + else + g_pStartNode = FindNode( GetRoot(), pszStartNode ); + + DumpNodes( (!g_pStartNode) ? GetRoot() : g_pStartNode, 0, true ); + Msg( _T("\n") ); + } + + int maxLen = ( type & VPRT_LIST_TOP_ITEMS_ONLY ) ? 25 : 999999; + + if ( type & VPRT_LIST_BY_TIME ) + { + DumpSorted( _T("-- Profile scopes sorted by time (including children) --"), GetTotalTimeSampled(), TimeCompare, maxLen ); + Msg( _T("\n") ); + } + if ( type & VPRT_LIST_BY_TIME_LESS_CHILDREN ) + { + DumpSorted( _T("-- Profile scopes sorted by time (without children) --"), GetTotalTimeSampled(), TimeLessChildrenCompare, maxLen ); + Msg( _T("\n") ); + } + if ( type & VPRT_LIST_BY_AVG_TIME ) + { + DumpSorted( _T("-- Profile scopes sorted by average time (including children) --"), GetTotalTimeSampled(), AverageTimeCompare, maxLen ); + Msg( _T("\n") ); + } + if ( type & VPRT_LIST_BY_AVG_TIME_LESS_CHILDREN ) + { + DumpSorted( _T("-- Profile scopes sorted by average time (without children) --"), GetTotalTimeSampled(), AverageTimeLessChildrenCompare, maxLen ); + Msg( _T("\n") ); + } + if ( type & VPRT_LIST_BY_PEAK_TIME ) + { + DumpSorted( _T("-- Profile scopes sorted by peak --"), GetTotalTimeSampled(), PeakCompare, maxLen); + Msg( _T("\n") ); + } + if ( type & VPRT_LIST_BY_PEAK_OVER_AVERAGE ) + { + DumpSorted( _T("-- Profile scopes sorted by peak over average (including children) --"), GetTotalTimeSampled(), PeakOverAverageCompare, maxLen ); + Msg( _T("\n") ); + } + + // TODO: Functions by time less children + // TODO: Functions by time averages + // TODO: Functions by peak + // TODO: Peak deviation from average + g_TimesLessChildren.clear(); + g_TimeSumsMap.clear(); + g_TimeSums.clear(); + +#ifdef _X360 + bool bPrintedHeader = true; + DumpPMC( FindNode( GetRoot(), pszStartNode ), bPrintedHeader ); +#endif + + } + Msg( _T("******** END VPROF REPORT ********\n")); + +} + +//============================================================================= + +CVProfile::CVProfile() + : m_Root( _T("Root"), 0, NULL, VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, 0 ), + m_pCurNode( &m_Root ), + m_nFrames( 0 ), + m_enabled( 0 ), // don't change this. if m_enabled is anything but zero coming out of this constructor, vprof will break. + m_pausedEnabledDepth( 0 ), + m_fAtRoot( true ) +{ + +#ifdef VPROF_VTUNE_GROUP + m_GroupIDStackDepth = 1; + m_GroupIDStack[0] = 0; // VPROF_BUDGETGROUP_OTHER_UNACCOUNTED +#endif + + m_TargetThreadId = ThreadGetCurrentId(); + + // Go ahead and allocate 32 slots for budget group names + MEM_ALLOC_CREDIT(); + m_pBudgetGroups = new CVProfile::CBudgetGroup[32]; + m_nBudgetGroupNames = 0; + m_nBudgetGroupNamesAllocated = 32; + + // Add these here so that they will always be in the same order. + // VPROF_BUDGETGROUP_OTHER_UNACCOUNTED has to be FIRST!!!! + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_WORLD_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_DISPLACEMENT_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_GAME, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_PLAYER, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_NPCS, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_SERVER_ANIM, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_CLIENT_ANIMATION, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_PHYSICS, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_STATICPROP_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_MODEL_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_MODEL_FAST_PATH_RENDERING,BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_LIGHTCACHE, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_BRUSHMODEL_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_SHADOW_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_DETAILPROP_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_PARTICLE_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_ROPES, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_DLIGHT_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_OTHER_NETWORKING, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_OTHER_SOUND, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_OTHER_VGUI, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_OTHER_FILESYSTEM, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_PREDICTION, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_INTERPOLATION, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_SWAP_BUFFERS, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_OCCLUSION, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_OVERLAYS, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_TOOLS, BUDGETFLAG_OTHER | BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_TEXTURE_CACHE, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_REPLAY, BUDGETFLAG_SERVER ); +// BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_DISP_HULLTRACES ); + + m_bPMEInit = false; + m_bPMEEnabled = false; + +#ifdef VPROF_VXCONSOLE_EXISTS + m_bTraceCompleteEvent = false; + m_iSuccessiveTraceIndex = 0; + m_ReportMode = VXCONSOLE_REPORT_TIME; + m_pReportScale[VXCONSOLE_REPORT_TIME] = 1000.0f; + m_pReportScale[VXCONSOLE_REPORT_L2CACHE_MISSES] = 1.0f; + m_pReportScale[VXCONSOLE_REPORT_LOAD_HIT_STORE] = 0.1f; + m_nFrameCount = 0; + m_nFramesRemaining = 1; + m_WorstCycles = 0; + m_WorstTraceFilename[ 0 ] = 0; + m_UpdateMode = 0; +#endif +#ifdef _X360 + m_iCPUTraceEnabled = kDisabled; +#endif +} + + +CVProfile::~CVProfile() +{ + Term(); +} + + +void CVProfile::FreeNodes_R( CVProfNode *pNode ) +{ + CVProfNode *pNext; + for ( CVProfNode *pChild = pNode->GetChild(); pChild; pChild = pNext ) + { + pNext = pChild->GetSibling(); + FreeNodes_R( pChild ); + } + + if ( pNode == GetRoot() ) + { + pNode->m_pChild = NULL; + } + else + { + delete pNode; + } +} + + +void CVProfile::Term() +{ + int i; + for( i = 0; i < m_nBudgetGroupNames; i++ ) + { + delete [] m_pBudgetGroups[i].m_pName; + } + delete m_pBudgetGroups; + m_nBudgetGroupNames = m_nBudgetGroupNamesAllocated = 0; + m_pBudgetGroups = NULL; + + int n; + for( n = 0; n < m_NumCounters; n++ ) + { + delete [] m_CounterNames[n]; + m_CounterNames[n] = NULL; + } + m_NumCounters = 0; + + // Free the nodes. + if ( GetRoot() ) + { + FreeNodes_R( GetRoot() ); + } +} + + +#define COLORMIN 160 +#define COLORMAX 255 + +static int g_ColorLookup[4] = +{ + COLORMIN, + COLORMAX, + COLORMIN+(COLORMAX-COLORMIN)/3, + COLORMIN+((COLORMAX-COLORMIN)*2)/3, +}; + +#define GET_BIT( val, bitnum ) ( ( val >> bitnum ) & 0x1 ) + +void CVProfile::GetBudgetGroupColor( int budgetGroupID, int &r, int &g, int &b, int &a ) +{ + budgetGroupID = budgetGroupID % ( 1 << 6 ); + + int index; + index = GET_BIT( budgetGroupID, 0 ) | ( GET_BIT( budgetGroupID, 5 ) << 1 ); + r = g_ColorLookup[index]; + index = GET_BIT( budgetGroupID, 1 ) | ( GET_BIT( budgetGroupID, 4 ) << 1 ); + g = g_ColorLookup[index]; + index = GET_BIT( budgetGroupID, 2 ) | ( GET_BIT( budgetGroupID, 3 ) << 1 ); + b = g_ColorLookup[index]; + a = 255; +} + +// return -1 if it doesn't exist. +int CVProfile::FindBudgetGroupName( const tchar *pBudgetGroupName ) +{ + int i; + for( i = 0; i < m_nBudgetGroupNames; i++ ) + { + if( _tcsicmp( pBudgetGroupName, m_pBudgetGroups[i].m_pName ) == 0 ) + { + return i; + } + } + return -1; +} + +int CVProfile::AddBudgetGroupName( const tchar *pBudgetGroupName, int budgetFlags ) +{ + MEM_ALLOC_CREDIT(); + tchar *pNewString = new tchar[ _tcslen( pBudgetGroupName ) + 1 ]; + _tcscpy( pNewString, pBudgetGroupName ); + if( m_nBudgetGroupNames + 1 > m_nBudgetGroupNamesAllocated ) + { + m_nBudgetGroupNamesAllocated *= 2; + m_nBudgetGroupNamesAllocated = max( m_nBudgetGroupNames + 6, m_nBudgetGroupNamesAllocated ); + + CBudgetGroup *pNew = new CBudgetGroup[ m_nBudgetGroupNamesAllocated ]; + for ( int i=0; i < m_nBudgetGroupNames; i++ ) + pNew[i] = m_pBudgetGroups[i]; + + delete [] m_pBudgetGroups; + m_pBudgetGroups = pNew; + } + + m_pBudgetGroups[m_nBudgetGroupNames].m_pName = pNewString; + m_pBudgetGroups[m_nBudgetGroupNames].m_BudgetFlags = budgetFlags; + m_nBudgetGroupNames++; + if( m_pNumBudgetGroupsChangedCallBack ) + { + (*m_pNumBudgetGroupsChangedCallBack)(); + } + +#if defined( VPROF_VXCONSOLE_EXISTS ) + // re-start with all the known budgets + VXProfileStart(); +#endif + return m_nBudgetGroupNames - 1; +} + +int CVProfile::BudgetGroupNameToBudgetGroupID( const tchar *pBudgetGroupName, int budgetFlagsToORIn ) +{ + int budgetGroupID = FindBudgetGroupName( pBudgetGroupName ); + if( budgetGroupID == -1 ) + { + budgetGroupID = AddBudgetGroupName( pBudgetGroupName, budgetFlagsToORIn ); + } + else + { + m_pBudgetGroups[budgetGroupID].m_BudgetFlags |= budgetFlagsToORIn; + } + + return budgetGroupID; +} + +int CVProfile::BudgetGroupNameToBudgetGroupID( const tchar *pBudgetGroupName ) +{ + return BudgetGroupNameToBudgetGroupID( pBudgetGroupName, BUDGETFLAG_OTHER ); +} + +int CVProfile::GetNumBudgetGroups( void ) +{ + return m_nBudgetGroupNames; +} + +void CVProfile::RegisterNumBudgetGroupsChangedCallBack( void (*pCallBack)(void) ) +{ + m_pNumBudgetGroupsChangedCallBack = pCallBack; +} + +void CVProfile::HideBudgetGroup( int budgetGroupID, bool bHide ) +{ + if( budgetGroupID != -1 ) + { + if ( bHide ) + m_pBudgetGroups[budgetGroupID].m_BudgetFlags |= BUDGETFLAG_HIDDEN; + else + m_pBudgetGroups[budgetGroupID].m_BudgetFlags &= ~BUDGETFLAG_HIDDEN; + } +} + +int *CVProfile::FindOrCreateCounter( const tchar *pName, CounterGroup_t eCounterGroup ) +{ + Assert( m_NumCounters+1 < MAXCOUNTERS ); + if ( m_NumCounters + 1 >= MAXCOUNTERS || !InTargetThread() ) + { + static int dummy; + return &dummy; + } + int i; + for( i = 0; i < m_NumCounters; i++ ) + { + if( _tcsicmp( m_CounterNames[i], pName ) == 0 ) + { + // found it! + return &m_Counters[i]; + } + } + + // NOTE: These get freed in ~CVProfile. + MEM_ALLOC_CREDIT(); + tchar *pNewName = new tchar[_tcslen( pName ) + 1]; + _tcscpy( pNewName, pName ); + m_Counters[m_NumCounters] = 0; + m_CounterGroups[m_NumCounters] = (char)eCounterGroup; + m_CounterNames[m_NumCounters++] = pNewName; + return &m_Counters[m_NumCounters-1]; +} + +void CVProfile::ResetCounters( CounterGroup_t eCounterGroup ) +{ + int i; + for( i = 0; i < m_NumCounters; i++ ) + { + if ( m_CounterGroups[i] == eCounterGroup ) + m_Counters[i] = 0; + } +} + +int CVProfile::GetNumCounters() const +{ + return m_NumCounters; +} + +const tchar *CVProfile::GetCounterName( int index ) const +{ + Assert( index >= 0 && index < m_NumCounters ); + return m_CounterNames[index]; +} + +int CVProfile::GetCounterValue( int index ) const +{ + Assert( index >= 0 && index < m_NumCounters ); + return m_Counters[index]; +} + +const tchar *CVProfile::GetCounterNameAndValue( int index, int &val ) const +{ + Assert( index >= 0 && index < m_NumCounters ); + val = m_Counters[index]; + return m_CounterNames[index]; +} + +CounterGroup_t CVProfile::GetCounterGroup( int index ) const +{ + Assert( index >= 0 && index < m_NumCounters ); + return (CounterGroup_t)m_CounterGroups[index]; +} + +#ifdef _X360 +void CVProfile::LatchMultiFrame( int64 cycles ) +{ + if ( cycles > m_WorstCycles ) + { + strncpy( m_WorstTraceFilename, GetCPUTraceFilename(), sizeof( m_WorstTraceFilename ) ); + m_WorstCycles = cycles; + } +} + +void CVProfile::SpewWorstMultiFrame() +{ + CCycleCount cc( m_WorstCycles ); + Msg( "%s == %.3f msec\n", m_WorstTraceFilename, cc.GetMillisecondsF() ); +} +#endif + +#ifdef DBGFLAG_VALIDATE + +#ifdef _WIN64 +#error the below is presumably broken on 64 bit +#endif // _WIN64 + +const int k_cSTLMapAllocOffset = 4; +#define GET_INTERNAL_MAP_ALLOC_PTR( pMap ) \ + ( * ( (void **) ( ( ( byte * ) ( pMap ) ) + k_cSTLMapAllocOffset ) ) ) +//----------------------------------------------------------------------------- +// Purpose: Ensure that all of our internal structures are consistent, and +// account for all memory that we've allocated. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void CVProfile::Validate( CValidator &validator, tchar *pchName ) +{ + validator.Push( _T("CVProfile"), this, pchName ); + + m_Root.Validate( validator, _T("m_Root") ); + + for ( int iBudgetGroup=0; iBudgetGroup < m_nBudgetGroupNames; iBudgetGroup++ ) + validator.ClaimMemory( m_pBudgetGroups[iBudgetGroup].m_pName ); + + validator.ClaimMemory( m_pBudgetGroups ); + + // The std template map class allocates memory internally, but offer no way to get + // access to their pointer. Since this is for debug purposes only and the + // std template classes don't change, just look at the well-known offset. This + // is arguably sick and wrong, kind of like marrying a squirrel. + validator.ClaimMemory( GET_INTERNAL_MAP_ALLOC_PTR( &g_TimesLessChildren ) ); + validator.ClaimMemory( GET_INTERNAL_MAP_ALLOC_PTR( &g_TimeSumsMap ) ); + + validator.Pop( ); +} +#endif // DBGFLAG_VALIDATE + +#endif + diff --git a/external/vpc/tier0/win32consoleio.cpp b/external/vpc/tier0/win32consoleio.cpp new file mode 100644 index 0000000..8ec6fa9 --- /dev/null +++ b/external/vpc/tier0/win32consoleio.cpp @@ -0,0 +1,161 @@ +//======= Copyright � 1996-2006, Valve Corporation, All rights reserved. ====== +// +// Purpose: Win32 Console API helpers +// +//============================================================================= + +#include "pch_tier0.h" +#include "win32consoleio.h" + +#if defined( _WIN32 ) + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <io.h> +#include <fcntl.h> + +#include <iostream> + +#endif // defined( _WIN32 ) + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// +// Attach a console to a Win32 GUI process and setup stdin, stdout & stderr +// along with the std::iostream (cout, cin, cerr) equivalents to read and +// write to and from that console +// +// 1. Ensure the handle associated with stdio is FILE_TYPE_UNKNOWN +// if it's anything else just return false. This supports cygwin +// style command shells like rxvt which setup pipes to processes +// they spawn +// +// 2. See if the Win32 function call AttachConsole exists in kernel32 +// It's a Windows 2000 and above call. If it does, call it and see +// if it succeeds in attaching to the console of the parent process. +// If that succeeds, return false (for no new console allocated). +// This supports someone typing the command from a normal windows +// command window and having the output go to the parent window. +// It's a little funny because a GUI app detaches so the command +// prompt gets intermingled with output from this process +// +// 3. If things get to here call AllocConsole which will pop open +// a new window and allow output to go to that window. The +// window will disappear when the process exists so if it's used +// for something like a help message then do something like getchar() +// from stdin to wait for a keypress. if AllocConsole is called +// true is returned. +// +// Return: true if AllocConsole() was used to pop open a new windows console +// +//----------------------------------------------------------------------------- +bool SetupWin32ConsoleIO() +{ +#if defined( _WIN32 ) + // Only useful on Windows platforms + + bool newConsole( false ); + + if ( GetFileType( GetStdHandle( STD_OUTPUT_HANDLE ) ) == FILE_TYPE_UNKNOWN ) + { + + HINSTANCE hInst = ::LoadLibrary( "kernel32.dll" ); + typedef BOOL ( WINAPI * pAttachConsole_t )( DWORD ); + pAttachConsole_t pAttachConsole( ( BOOL ( _stdcall * )( DWORD ) )GetProcAddress( hInst, "AttachConsole" ) ); + + if ( !( pAttachConsole && (*pAttachConsole)( ( DWORD ) - 1 ) ) ) + { + newConsole = true; + AllocConsole(); + } + + *stdout = *_fdopen( _open_osfhandle( reinterpret_cast< intp >( GetStdHandle( STD_OUTPUT_HANDLE ) ), _O_TEXT ), "w" ); + setvbuf( stdout, NULL, _IONBF, 0 ); + + *stdin = *_fdopen( _open_osfhandle( reinterpret_cast< intp >( GetStdHandle( STD_INPUT_HANDLE ) ), _O_TEXT ), "r" ); + setvbuf( stdin, NULL, _IONBF, 0 ); + + *stderr = *_fdopen( _open_osfhandle( reinterpret_cast< intp >( GetStdHandle( STD_ERROR_HANDLE ) ), _O_TEXT ), "w" ); + setvbuf( stdout, NULL, _IONBF, 0 ); + + std::ios_base::sync_with_stdio(); + } + + return newConsole; + +#else // defined( _WIN32 ) + + return false; + +#endif // defined( _WIN32 ) +} + +//----------------------------------------------------------------------------- +// Win32 Console Color API Helpers, originally from cmdlib. +// Retrieves the current console color attributes. +//----------------------------------------------------------------------------- +void InitWin32ConsoleColorContext( Win32ConsoleColorContext_t *pContext ) +{ +#if PLATFORM_WINDOWS_PC + // Get the old background attributes. + CONSOLE_SCREEN_BUFFER_INFO oldInfo; + GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &oldInfo ); + pContext->m_InitialColor = pContext->m_LastColor = oldInfo.wAttributes & (FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY); + pContext->m_BackgroundFlags = oldInfo.wAttributes & (BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE|BACKGROUND_INTENSITY); + + pContext->m_BadColor = 0; + if (pContext->m_BackgroundFlags & BACKGROUND_RED) + pContext->m_BadColor |= FOREGROUND_RED; + if (pContext->m_BackgroundFlags & BACKGROUND_GREEN) + pContext->m_BadColor |= FOREGROUND_GREEN; + if (pContext->m_BackgroundFlags & BACKGROUND_BLUE) + pContext->m_BadColor |= FOREGROUND_BLUE; + if (pContext->m_BackgroundFlags & BACKGROUND_INTENSITY) + pContext->m_BadColor |= FOREGROUND_INTENSITY; +#else + pContext->m_InitialColor = 0; +#endif +} + +//----------------------------------------------------------------------------- +// Sets the active console foreground color. This function is smart enough to +// avoid setting the color to something that would be unreadable given +// the user's potentially customized background color. It leaves the +// background color unchanged. +// Returns: The console's previous foreground color. +//----------------------------------------------------------------------------- +uint16 SetWin32ConsoleColor( Win32ConsoleColorContext_t *pContext, int nRed, int nGreen, int nBlue, int nIntensity ) +{ +#if PLATFORM_WINDOWS_PC + uint16 ret = pContext->m_LastColor; + pContext->m_LastColor = 0; + if ( nRed ) pContext->m_LastColor |= FOREGROUND_RED; + if ( nGreen ) pContext->m_LastColor |= FOREGROUND_GREEN; + if ( nBlue ) pContext->m_LastColor |= FOREGROUND_BLUE; + if ( nIntensity ) pContext->m_LastColor |= FOREGROUND_INTENSITY; + + // Just use the initial color if there's a match... + if ( pContext->m_LastColor == pContext->m_BadColor ) + pContext->m_LastColor = pContext->m_InitialColor; + + SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), pContext->m_LastColor | pContext->m_BackgroundFlags ); + return ret; +#else + return 0; +#endif +} + +//----------------------------------------------------------------------------- +// Restore's the active foreground console color, without distributing the current +// background color. +//----------------------------------------------------------------------------- +void RestoreWin32ConsoleColor( Win32ConsoleColorContext_t *pContext, uint16 prevColor ) +{ +#if PLATFORM_WINDOWS_PC + SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), prevColor | pContext->m_BackgroundFlags ); + pContext->m_LastColor = prevColor; +#endif +} |