diff --git a/r5dev/public/imemalloc.h b/r5dev/public/imemalloc.h new file mode 100644 index 00000000..db530823 --- /dev/null +++ b/r5dev/public/imemalloc.h @@ -0,0 +1,105 @@ +//====== Copyright � 1996-2005, Valve Corporation, All rights reserved. =======// +// +// Purpose: This header should never be used directly from leaf code!!! +// Instead, just add the file memoverride.cpp into your project and all this +// will automagically be used +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TIER0_IMEMALLOC_H +#define TIER0_IMEMALLOC_H + +/// This interface class is used to let the mem_dump command retrieve +/// information about memory allocations outside of the heap. It is currently +/// used by CMemoryStack to report on its allocations. +abstract_class IMemoryInfo +{ +public: + virtual const char* GetMemoryName() const = 0; // User friendly name for this stack or pool + virtual size_t GetAllocatedBytes() const = 0; // Number of bytes currently allocated + virtual size_t GetCommittedBytes() const = 0; // Bytes committed -- may be greater than allocated. + virtual size_t GetReservedBytes() const = 0; // Bytes reserved -- may be greater than committed. + virtual size_t GetHighestBytes() const = 0; // The maximum number of bytes allocated or committed. +}; + +//----------------------------------------------------------------------------- + +#if (defined(_DEBUG) || defined(USE_MEM_DEBUG)) +struct MemAllocFileLine_t +{ + const char* pszFile; + int line; +}; + +#define MEMALLOC_DEFINE_EXTERNAL_TRACKING( tag ) \ + static CUtlMap s_##tag##Allocs( DefLessFunc( void *) ); \ + CUtlMap * g_p##tag##Allocs = &s_##tag##Allocs; \ + static CThreadFastMutex s_##tag##AllocsMutex; \ + CThreadFastMutex * g_p##tag##AllocsMutex = &s_##tag##AllocsMutex; \ + const char * g_psz##tag##Alloc = strcpy( (char *)MemAlloc_Alloc( strlen( #tag "Alloc" ) + 1, "intentional leak", 0 ), #tag "Alloc" ); + +#define MEMALLOC_DECLARE_EXTERNAL_TRACKING( tag ) \ + extern CUtlMap * g_p##tag##Allocs; \ + extern CThreadFastMutex *g_p##tag##AllocsMutex; \ + extern const char * g_psz##tag##Alloc; + +#define MemAlloc_RegisterExternalAllocation( tag, p, size ) \ + if ( !p ) \ + ; \ + else \ + { \ + AUTO_LOCK_FM( *g_p##tag##AllocsMutex ); \ + MemAllocFileLine_t fileLine = { g_psz##tag##Alloc, 0 }; \ + g_pMemAlloc->GetActualDbgInfo( fileLine.pszFile, fileLine.line ); \ + if ( fileLine.pszFile != g_psz##tag##Alloc ) \ + { \ + g_p##tag##Allocs->Insert( p, fileLine ); \ + } \ + \ + MemAlloc_RegisterAllocation( fileLine.pszFile, fileLine.line, size, size, 0); \ + } + +#define MemAlloc_RegisterExternalDeallocation( tag, p, size ) \ + if ( !p ) \ + ; \ + else \ + { \ + AUTO_LOCK_FM( *g_p##tag##AllocsMutex ); \ + MemAllocFileLine_t fileLine = { g_psz##tag##Alloc, 0 }; \ + CUtlMap::IndexType_t iRecordedFileLine = g_p##tag##Allocs->Find( p ); \ + if ( iRecordedFileLine != g_p##tag##Allocs->InvalidIndex() ) \ + { \ + fileLine = (*g_p##tag##Allocs)[iRecordedFileLine]; \ + g_p##tag##Allocs->RemoveAt( iRecordedFileLine ); \ + } \ + \ + MemAlloc_RegisterDeallocation( fileLine.pszFile, fileLine.line, size, size, 0); \ + } + +#else + +#define MEMALLOC_DEFINE_EXTERNAL_TRACKING( tag ) +#define MEMALLOC_DECLARE_EXTERNAL_TRACKING( tag ) +#define MemAlloc_RegisterExternalAllocation( tag, p, size ) ((void)0) +#define MemAlloc_RegisterExternalDeallocation( tag, p, size ) ((void)0) + +#endif + +//----------------------------------------------------------------------------- + +#if (defined(_DEBUG) || defined(USE_MEM_DEBUG)) +#define MEM_ALLOC_CREDIT_(tag) CMemAllocAttributeAlloction memAllocAttributeAlloction( tag, __LINE__ ) +#define MemAlloc_PushAllocDbgInfo( pszFile, line ) g_pMemAlloc->PushAllocDbgInfo( pszFile, line ) +#define MemAlloc_PopAllocDbgInfo() g_pMemAlloc->PopAllocDbgInfo() +#define MemAlloc_RegisterAllocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ) g_pMemAlloc->RegisterAllocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ) +#define MemAlloc_RegisterDeallocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ) g_pMemAlloc->RegisterDeallocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ) +#else +#define MEM_ALLOC_CREDIT_(tag) ((void)0) +#define MemAlloc_PushAllocDbgInfo( pszFile, line ) ((void)0) +#define MemAlloc_PopAllocDbgInfo() ((void)0) +#define MemAlloc_RegisterAllocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ) ((void)0) +#define MemAlloc_RegisterDeallocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ) ((void)0) +#endif + +#endif /* TIER0_MEMALLOC_H */ \ No newline at end of file diff --git a/r5dev/tier0/basetypes.h b/r5dev/tier0/basetypes.h index fec86049..54d8c2df 100644 --- a/r5dev/tier0/basetypes.h +++ b/r5dev/tier0/basetypes.h @@ -217,6 +217,12 @@ void Swap(T& a, T& b) b = temp; } +template +inline T AlignValue(T val, uintptr_t alignment) +{ + return (T)(((uintp)val + alignment - 1) & ~(alignment - 1)); +} + #else #define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val))) diff --git a/r5dev/tier1/memstack.cpp b/r5dev/tier1/memstack.cpp new file mode 100644 index 00000000..54a6247c --- /dev/null +++ b/r5dev/tier1/memstack.cpp @@ -0,0 +1,488 @@ +//====== Copyright � 1996-2005, Valve Corporation, All rights reserved. =======// +// +// Purpose: +// +//=============================================================================// + +#include "core/stdafx.h" +#include "tier0/dbg.h" +#include "tier0/memstd.h" +#include "memstack.h" +#include "utlmap.h" +//#include "tier0/memdbgon.h" + +#ifdef _WIN32 +#pragma warning(disable:4073) +#pragma init_seg(lib) +#endif + +#define VA_COMMIT_FLAGS MEM_COMMIT +#define VA_RESERVE_FLAGS MEM_RESERVE + +static volatile bool bSpewAllocations = false; // TODO: Register CMemoryStacks with g_pMemAlloc, so it can spew a summary + +//----------------------------------------------------------------------------- + +MEMALLOC_DEFINE_EXTERNAL_TRACKING(CMemoryStack); + +//----------------------------------------------------------------------------- + +void PrintStatus( void* p ) +{ + CMemoryStack* pMemoryStack = (CMemoryStack*)p; + + pMemoryStack->PrintContents(); +} + +CMemoryStack::CMemoryStack() + : m_pNextAlloc( NULL ) + , m_pCommitLimit( NULL ) + , m_pAllocLimit( NULL ) + , m_pHighestAllocLimit( NULL ) + , m_pBase( NULL ) + , m_bRegisteredAllocation( false ) + , m_maxSize( 0 ) + , m_alignment( 16 ) +#ifdef MEMSTACK_VIRTUAL_MEMORY_AVAILABLE + , m_commitIncrement( 0 ) + , m_minCommit( 0 ) + #ifdef _PS3 + , m_pVirtualMemorySection( NULL ) + #endif +#endif +{ + AddMemoryInfoCallback( this ); + m_pszAllocOwner = strdup( "CMemoryStack unattributed" ); +} + +//------------------------------------- + +CMemoryStack::~CMemoryStack() +{ + if ( m_pBase ) + Term(); + + RemoveMemoryInfoCallback( this ); + free( m_pszAllocOwner ); +} + +//------------------------------------- + +bool CMemoryStack::Init( const char *pszAllocOwner, unsigned maxSize, unsigned commitIncrement, unsigned initialCommit, unsigned alignment ) +{ + Assert( !m_pBase ); + + m_bPhysical = false; + + m_maxSize = maxSize; + m_alignment = AlignValue( alignment, 4 ); + + Assert( m_alignment == alignment ); + Assert( m_maxSize > 0 ); + + SetAllocOwner( pszAllocOwner ); + +#ifdef MEMSTACK_VIRTUAL_MEMORY_AVAILABLE + +#ifdef _PS3 + // Memory can only be committed in page-size increments on PS3 + static const unsigned PS3_PAGE_SIZE = 64*1024; + if ( commitSize < PS3_PAGE_SIZE ) + commitSize = PS3_PAGE_SIZE; +#endif + + if ( commitIncrement != 0 ) + { + m_commitIncrement = commitIncrement; + } + + unsigned pageSize; + +#ifdef _PS3 + pageSize = PS3_PAGE_SIZE; +#elif defined( _X360 ) + pageSize = 64 * 1024; +#else + SYSTEM_INFO sysInfo; + GetSystemInfo( &sysInfo ); + Assert( !( sysInfo.dwPageSize & (sysInfo.dwPageSize-1)) ); + pageSize = sysInfo.dwPageSize; +#endif + + if ( m_commitIncrement == 0 ) + { + m_commitIncrement = pageSize; + } + else + { + m_commitIncrement = AlignValue( m_commitIncrement, pageSize ); + } + + m_maxSize = AlignValue( m_maxSize, m_commitIncrement ); + + Assert( m_maxSize % pageSize == 0 && m_commitIncrement % pageSize == 0 && m_commitIncrement <= m_maxSize ); + +#ifdef _WIN32 + m_pBase = (unsigned char *)VirtualAlloc( NULL, m_maxSize, VA_RESERVE_FLAGS, PAGE_NOACCESS ); +#else + m_pVirtualMemorySection = g_pMemAlloc->AllocateVirtualMemorySection( m_maxSize ); + if ( !m_pVirtualMemorySection ) + { + Warning( "AllocateVirtualMemorySection failed( size=%d )\n", m_maxSize ); + Assert( 0 ); + m_pBase = NULL; + } + else + { + m_pBase = ( byte* ) m_pVirtualMemorySection->GetBaseAddress(); + } +#endif + if ( !m_pBase ) + { +#if !defined( NO_MALLOC_OVERRIDE ) + g_pMemAlloc->OutOfMemory(); +#endif + return false; + } + m_pCommitLimit = m_pNextAlloc = m_pBase; + + if ( initialCommit ) + { + initialCommit = AlignValue( initialCommit, m_commitIncrement ); + Assert( initialCommit <= m_maxSize ); + bool bInitialCommitSucceeded = false; +#ifdef _WIN32 + bInitialCommitSucceeded = !!VirtualAlloc( m_pCommitLimit, initialCommit, VA_COMMIT_FLAGS, PAGE_READWRITE ); +#else + m_pVirtualMemorySection->CommitPages( m_pCommitLimit, initialCommit ); + bInitialCommitSucceeded = true; +#endif + if ( !bInitialCommitSucceeded ) + { +#if !defined( NO_MALLOC_OVERRIDE ) + g_pMemAlloc->OutOfMemory( initialCommit ); +#endif + return false; + } + m_minCommit = initialCommit; + m_pCommitLimit += initialCommit; + RegisterAllocation(); + } + +#else + m_pBase = (byte*)MemAlloc_AllocAligned( m_maxSize, alignment ? alignment : 1 ); + m_pNextAlloc = m_pBase; + m_pCommitLimit = m_pBase + m_maxSize; +#endif + + m_pHighestAllocLimit = m_pNextAlloc; + + m_pAllocLimit = m_pBase + m_maxSize; + + return ( m_pBase != NULL ); +} + +//------------------------------------- + +#ifdef _GAMECONSOLE +bool CMemoryStack::InitPhysical( const char *pszAllocOwner, uint size, uint nBaseAddrAlignment, uint alignment, uint32 nFlags ) +{ + m_bPhysical = true; + + m_maxSize = m_commitIncrement = size; + m_alignment = AlignValue( alignment, 4 ); + + SetAllocOwner( pszAllocOwner ); + +#ifdef _X360 + int flags = PAGE_READWRITE | nFlags; + if ( size >= 16*1024*1024 ) + { + flags |= MEM_16MB_PAGES; + } + else + { + flags |= MEM_LARGE_PAGES; + } + m_pBase = (unsigned char *)XPhysicalAlloc( m_maxSize, MAXULONG_PTR, nBaseAddrAlignment, flags ); +#elif defined (_PS3) + m_pBase = (byte*)nFlags; + m_pBase = (byte*)AlignValue( (uintp)m_pBase, m_alignment ); +#else +#pragma error +#endif + + Assert( m_pBase ); + m_pNextAlloc = m_pBase; + m_pCommitLimit = m_pBase + m_maxSize; + m_pAllocLimit = m_pBase + m_maxSize; + m_pHighestAllocLimit = m_pNextAlloc; + + RegisterAllocation(); + return ( m_pBase != NULL ); +} +#endif + +//------------------------------------- + +void CMemoryStack::Term() +{ + FreeAll(); + if ( m_pBase ) + { +#ifdef _GAMECONSOLE + if ( m_bPhysical ) + { +#if defined( _X360 ) + XPhysicalFree( m_pBase ); +#elif defined( _PS3 ) +#else +#pragma error +#endif + m_pCommitLimit = m_pBase = NULL; + m_maxSize = 0; + RegisterDeallocation(true); + m_bPhysical = false; + return; + } +#endif // _GAMECONSOLE + +#ifdef MEMSTACK_VIRTUAL_MEMORY_AVAILABLE +#if defined(_WIN32) + VirtualFree( m_pBase, 0, MEM_RELEASE ); +#else + m_pVirtualMemorySection->Release(); + m_pVirtualMemorySection = NULL; +#endif +#else + MemAlloc_FreeAligned( m_pBase ); +#endif + m_pBase = NULL; + // Zero these variables to avoid getting misleading mem_dump + // results when m_pBase is NULL. + m_pNextAlloc = NULL; + m_pCommitLimit = NULL; + m_pHighestAllocLimit = NULL; + m_maxSize = 0; + RegisterDeallocation(true); + } +} + +//------------------------------------- + +int CMemoryStack::GetSize() const +{ + if ( m_bPhysical ) + return m_maxSize; + +#ifdef MEMSTACK_VIRTUAL_MEMORY_AVAILABLE + return m_pCommitLimit - m_pBase; +#else + return m_maxSize; +#endif +} + + +//------------------------------------- + +bool CMemoryStack::CommitTo( byte *pNextAlloc ) RESTRICT +{ + if ( m_bPhysical ) + { + return NULL; + } + +#ifdef MEMSTACK_VIRTUAL_MEMORY_AVAILABLE + unsigned char * pNewCommitLimit = AlignValue( pNextAlloc, m_commitIncrement ); + ptrdiff_t commitIncrement = pNewCommitLimit - m_pCommitLimit; + + if( m_pCommitLimit + commitIncrement > m_pAllocLimit ) + { +#if !defined( NO_MALLOC_OVERRIDE ) + g_pMemAlloc->OutOfMemory( commitIncrement ); +#endif + return false; + } + + if ( pNewCommitLimit > m_pCommitLimit ) + { + RegisterDeallocation(false); + bool bAllocationSucceeded = false; +#ifdef _WIN32 + bAllocationSucceeded = !!VirtualAlloc( m_pCommitLimit, commitIncrement, VA_COMMIT_FLAGS, PAGE_READWRITE ); +#else + bAllocationSucceeded = m_pVirtualMemorySection->CommitPages( m_pCommitLimit, commitIncrement ); +#endif + if ( !bAllocationSucceeded ) + { +#if !defined( NO_MALLOC_OVERRIDE ) + g_pMemAlloc->OutOfMemory( commitIncrement ); +#endif + return false; + } + m_pCommitLimit = pNewCommitLimit; + + RegisterAllocation(); + } + else if ( pNewCommitLimit < m_pCommitLimit ) + { + if ( m_pNextAlloc > pNewCommitLimit ) + { + Warning( eDLL_T::COMMON, "ATTEMPTED TO DECOMMIT OWNED MEMORY STACK SPACE\n" ); + pNewCommitLimit = AlignValue( m_pNextAlloc, m_commitIncrement ); + } + + if ( pNewCommitLimit < m_pCommitLimit ) + { + RegisterDeallocation(false); + ptrdiff_t decommitIncrement = m_pCommitLimit - pNewCommitLimit; +#ifdef _WIN32 + VirtualFree( pNewCommitLimit, decommitIncrement, MEM_DECOMMIT ); +#else + m_pVirtualMemorySection->DecommitPages( pNewCommitLimit, decommitIncrement ); +#endif + m_pCommitLimit = pNewCommitLimit; + RegisterAllocation(); + } + } + + return true; +#else + return false; +#endif +} + +// Identify the owner of this memory stack's memory +void CMemoryStack::SetAllocOwner( const char *pszAllocOwner ) +{ + if ( !pszAllocOwner || !Q_strcmp( m_pszAllocOwner, pszAllocOwner ) ) + return; + free( m_pszAllocOwner ); + m_pszAllocOwner = strdup( pszAllocOwner ); +} + +void CMemoryStack::RegisterAllocation() +{ + // 'physical' allocations on PS3 come from RSX local memory, so we don't count them here: + if ( IsPS3() && m_bPhysical ) + return; + + if ( GetSize() ) + { + if ( m_bRegisteredAllocation ) + Warning( eDLL_T::COMMON, "CMemoryStack: ERROR - mismatched RegisterAllocation/RegisterDeallocation!\n" ); + + // NOTE: we deliberately don't use MemAlloc_RegisterExternalAllocation. CMemoryStack needs to bypass 'GetActualDbgInfo' + // due to the way it allocates memory: there's just one representative memory address (m_pBase), it grows at unpredictable + // times (in CommitTo, not every Alloc call) and it is freed en-masse (instead of freeing each individual allocation). + MemAlloc_RegisterAllocation( m_pszAllocOwner, 0, GetSize(), GetSize(), 0 ); + } + m_bRegisteredAllocation = true; + + // Temp memorystack spew: very useful when we crash out of memory + if ( IsGameConsole() && bSpewAllocations ) DevMsg( eDLL_T::COMMON, "CMemoryStack: %4.1fMB (%s)\n", GetSize()/(float)(1024*1024), m_pszAllocOwner ); +} + +void CMemoryStack::RegisterDeallocation( bool bShouldSpewSize ) +{ + // 'physical' allocations on PS3 come from RSX local memory, so we don't count them here: + if ( IsPS3() && m_bPhysical ) + return; + + if ( GetSize() ) + { + if ( !m_bRegisteredAllocation ) + Warning( eDLL_T::COMMON, "CMemoryStack: ERROR - mismatched RegisterAllocation/RegisterDeallocation!\n" ); + MemAlloc_RegisterDeallocation( m_pszAllocOwner, 0, GetSize(), GetSize(), 0 ); + } + m_bRegisteredAllocation = false; + + // Temp memorystack spew: very useful when we crash out of memory + if ( bShouldSpewSize && IsGameConsole() && bSpewAllocations ) DevMsg( eDLL_T::COMMON, "CMemoryStack: %4.1fMB (%s)\n", GetSize()/(float)(1024*1024), m_pszAllocOwner ); +} + +//------------------------------------- + +void CMemoryStack::FreeToAllocPoint( MemoryStackMark_t mark, bool bDecommit ) +{ + mark = AlignValue( mark, m_alignment ); + byte *pAllocPoint = m_pBase + mark; + + Assert( pAllocPoint >= m_pBase && pAllocPoint <= m_pNextAlloc ); + if ( pAllocPoint >= m_pBase && pAllocPoint <= m_pNextAlloc ) + { + m_pNextAlloc = pAllocPoint; +#ifdef MEMSTACK_VIRTUAL_MEMORY_AVAILABLE + if ( bDecommit && !m_bPhysical ) + { + CommitTo( MAX( m_pNextAlloc, (m_pBase + m_minCommit) ) ); + } +#endif + } +} + +//------------------------------------- + +void CMemoryStack::FreeAll( bool bDecommit ) +{ + if ( m_pBase && ( m_pBase < m_pCommitLimit ) ) + { + FreeToAllocPoint( 0, bDecommit ); + } +} + +//------------------------------------- + +void CMemoryStack::Access( void **ppRegion, unsigned *pBytes ) +{ + *ppRegion = m_pBase; + *pBytes = ( m_pNextAlloc - m_pBase); +} + +const char* CMemoryStack::GetMemoryName() const +{ + return m_pszAllocOwner; +} + +size_t CMemoryStack::GetAllocatedBytes() const +{ + return GetUsed(); +} + +size_t CMemoryStack::GetCommittedBytes() const +{ + return GetSize(); +} + +size_t CMemoryStack::GetReservedBytes() const +{ + return GetMaxSize(); +} + +//size_t CMemoryStack::GetHighestBytes() const +//{ +// size_t highest = m_pHighestAllocLimit - m_pBase; +// return highest; +//} + +//------------------------------------- + +//void CMemoryStack::PrintContents() const +//{ +// size_t highest = m_pHighestAllocLimit - m_pBase; +// MEMORY_BASIC_INFORMATION info; +// char moduleName[260]; +// strcpy( moduleName, "unknown module" ); +// // Because this code is statically linked into each DLL, this function and the PrintStatus +// // function will be in the DLL that constructed the CMemoryStack object. We can then +// // retrieve the DLL name to give slightly more verbose memory dumps. +// if ( VirtualQuery( &PrintStatus, &info, sizeof( info ) ) == sizeof( info ) ) +// { +// GetModuleFileNameA( (HMODULE) info.AllocationBase, moduleName, _countof( moduleName ) ); +// moduleName[ _countof( moduleName )-1 ] = 0; +// } +// DevMsg( eDLL_T::COMMON, "CMemoryStack %s in %s\n", m_pszAllocOwner, moduleName ); +// DevMsg( eDLL_T::COMMON, " Total used memory: %d KB\n", GetUsed() / 1024 ); +// DevMsg( eDLL_T::COMMON, " Total committed memory: %d KB\n", GetSize() / 1024 ); +// DevMsg( eDLL_T::COMMON, " Max committed memory: %u KB out of %d KB\n", (unsigned)highest / 1024, GetMaxSize() / 1024 ); +//} diff --git a/r5dev/tier1/memstack.h b/r5dev/tier1/memstack.h new file mode 100644 index 00000000..45302b60 --- /dev/null +++ b/r5dev/tier1/memstack.h @@ -0,0 +1,244 @@ +//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: A fast stack memory allocator that uses virtual memory if available +// +//===========================================================================// + +#ifndef MEMSTACK_H +#define MEMSTACK_H + +#if defined( _WIN32 ) +#pragma once +#endif + +#include "tier1/utlvector.h" +#include "public/imemalloc.h" + +#if defined( _WIN32 ) || defined( _PS3 ) +#define MEMSTACK_VIRTUAL_MEMORY_AVAILABLE +#endif + +//----------------------------------------------------------------------------- + +typedef unsigned MemoryStackMark_t; + +class CMemoryStack : private IMemoryInfo +{ +public: + CMemoryStack(); + ~CMemoryStack(); + + bool Init( const char *pszAllocOwner, unsigned maxSize = 0, unsigned commitIncrement = 0, unsigned initialCommit = 0, unsigned alignment = 16 ); +#ifdef _GAMECONSOLE + bool InitPhysical( const char *pszAllocOwner, uint size, uint nBaseAddrAlignment, uint alignment = 16, uint32 nAdditionalFlags = 0 ); +#endif + void Term(); + + int GetSize() const; + int GetMaxSize() const ; + int GetUsed() const; + + void *Alloc( unsigned bytes, bool bClear = false ) RESTRICT; + + MemoryStackMark_t GetCurrentAllocPoint() const; + void FreeToAllocPoint( MemoryStackMark_t mark, bool bDecommit = true ); + void FreeAll( bool bDecommit = true ); + + void Access( void **ppRegion, unsigned *pBytes ); + + void PrintContents() const; + + void *GetBase(); + const void *GetBase() const { return const_cast(this)->GetBase(); } + + bool CommitSize( int nBytes ); + + void SetAllocOwner( const char *pszAllocOwner ); + +private: + bool CommitTo( byte * ) RESTRICT; + void RegisterAllocation(); + void RegisterDeallocation( bool bShouldSpew ); + + const char* GetMemoryName() const OVERRIDE; // User friendly name for this stack or pool + size_t GetAllocatedBytes() const OVERRIDE; // Number of bytes currently allocated + size_t GetCommittedBytes() const OVERRIDE; // Bytes committed -- may be greater than allocated. + size_t GetReservedBytes() const OVERRIDE; // Bytes reserved -- may be greater than committed. + size_t GetHighestBytes() const OVERRIDE; // The maximum number of bytes allocated or committed. + + byte *m_pNextAlloc; // Current alloc point (m_pNextAlloc - m_pBase == allocated bytes) + byte *m_pCommitLimit; // The current end of the committed memory. On systems without dynamic commit/decommit this is always m_pAllocLimit + byte *m_pAllocLimit; // The top of the allocated address space (m_pBase + m_maxSize) + // Track the highest alloc limit seen. + byte *m_pHighestAllocLimit; + + byte *m_pBase; + bool m_bRegisteredAllocation; + bool m_bPhysical; + char *m_pszAllocOwner; + + unsigned m_maxSize; // m_maxSize stores how big the stack can grow. It measures the reservation size. + unsigned m_alignment; +#ifdef MEMSTACK_VIRTUAL_MEMORY_AVAILABLE + unsigned m_commitIncrement; + unsigned m_minCommit; +#endif +#if defined( MEMSTACK_VIRTUAL_MEMORY_AVAILABLE ) && defined( _PS3 ) + IVirtualMemorySection *m_pVirtualMemorySection; +#endif + +private: + // Make the assignment operator and copy constructor private and unimplemented. + CMemoryStack& operator=( const CMemoryStack& ); + CMemoryStack( const CMemoryStack& ); +}; + +//------------------------------------- + +FORCEINLINE void *CMemoryStack::Alloc( unsigned bytes, bool bClear ) RESTRICT +{ + Assert( m_pBase ); + + bytes = MAX( bytes, m_alignment ); + bytes = AlignValue( bytes, m_alignment ); + + void *pResult = m_pNextAlloc; + byte *pNextAlloc = m_pNextAlloc + bytes; + + if ( pNextAlloc > m_pCommitLimit ) + { + if ( !CommitTo( pNextAlloc ) ) + { + return NULL; + } + } + + if ( bClear ) + { + memset( pResult, 0, bytes ); + } + + m_pNextAlloc = pNextAlloc; + m_pHighestAllocLimit = Max( m_pNextAlloc, m_pHighestAllocLimit ); + + return pResult; +} + +//------------------------------------- + +inline bool CMemoryStack::CommitSize( int nBytes ) +{ + if ( GetSize() != nBytes ) + { + return CommitTo( m_pBase + nBytes ); + } + return true; +} + +//------------------------------------- + +// How big can this memory stack grow? This is equivalent to how many +// bytes are reserved. +inline int CMemoryStack::GetMaxSize() const +{ + return m_maxSize; +} + +//------------------------------------- + +inline int CMemoryStack::GetUsed() const +{ + return ( m_pNextAlloc - m_pBase ); +} + +//------------------------------------- + +inline void *CMemoryStack::GetBase() +{ + return m_pBase; +} + +//------------------------------------- + +inline MemoryStackMark_t CMemoryStack::GetCurrentAllocPoint() const +{ + return ( m_pNextAlloc - m_pBase ); +} + + +//----------------------------------------------------------------------------- +// The CUtlMemoryStack class: +// A fixed memory class +//----------------------------------------------------------------------------- +template< typename T, typename I, size_t MAX_SIZE, size_t COMMIT_SIZE = 0, size_t INITIAL_COMMIT = 0 > +class CUtlMemoryStack +{ +public: + // constructor, destructor + CUtlMemoryStack( int nGrowSize = 0, int nInitSize = 0 ) { m_MemoryStack.Init( "CUtlMemoryStack", MAX_SIZE * sizeof(T), COMMIT_SIZE * sizeof(T), INITIAL_COMMIT * sizeof(T), 4 ); COMPILE_TIME_ASSERT( sizeof(T) % 4 == 0 ); } + CUtlMemoryStack( T* pMemory, int numElements ) { Assert( 0 ); } + + // Can we use this index? + bool IsIdxValid( I i ) const { long x=i; return (x >= 0) && (x < m_nAllocated); } + + // Specify the invalid ('null') index that we'll only return on failure + static const I INVALID_INDEX = ( I )-1; // For use with COMPILE_TIME_ASSERT + static I InvalidIndex() { return INVALID_INDEX; } + + class Iterator_t + { + Iterator_t( I i ) : index( i ) {} + I index; + friend class CUtlMemoryStack; + public: + bool operator==( const Iterator_t it ) const { return index == it.index; } + bool operator!=( const Iterator_t it ) const { return index != it.index; } + }; + Iterator_t First() const { return Iterator_t( m_nAllocated ? 0 : InvalidIndex() ); } + Iterator_t Next( const Iterator_t &it ) const { return Iterator_t( it.index < m_nAllocated ? it.index + 1 : InvalidIndex() ); } + I GetIndex( const Iterator_t &it ) const { return it.index; } + bool IsIdxAfter( I i, const Iterator_t &it ) const { return i > it.index; } + bool IsValidIterator( const Iterator_t &it ) const { long x=it.index; return x >= 0 && x < m_nAllocated; } + Iterator_t InvalidIterator() const { return Iterator_t( InvalidIndex() ); } + + // Gets the base address + T* Base() { return (T*)m_MemoryStack.GetBase(); } + const T* Base() const { return (const T*)m_MemoryStack.GetBase(); } + + // element access + T& operator[]( I i ) { Assert( IsIdxValid(i) ); return Base()[i]; } + const T& operator[]( I i ) const { Assert( IsIdxValid(i) ); return Base()[i]; } + T& Element( I i ) { Assert( IsIdxValid(i) ); return Base()[i]; } + const T& Element( I i ) const { Assert( IsIdxValid(i) ); return Base()[i]; } + + // Attaches the buffer to external memory.... + void SetExternalBuffer( T* pMemory, int numElements ) { Assert( 0 ); } + + // Size + int NumAllocated() const { return m_nAllocated; } + int Count() const { return m_nAllocated; } + + // Grows the memory, so that at least allocated + num elements are allocated + void Grow( int num = 1 ) { Assert( num > 0 ); m_nAllocated += num; m_MemoryStack.Alloc( num * sizeof(T) ); } + + // Makes sure we've got at least this much memory + void EnsureCapacity( int num ) { Assert( num <= MAX_SIZE ); if ( m_nAllocated < num ) Grow( num - m_nAllocated ); } + + // Memory deallocation + void Purge() { m_MemoryStack.FreeAll(); m_nAllocated = 0; } + + // is the memory externally allocated? + bool IsExternallyAllocated() const { return false; } + + // Set the size by which the memory grows + void SetGrowSize( int size ) { Assert( 0 ); } + + // Identify the owner of this memory stack's memory + void SetAllocOwner( const char *pszAllocOwner ) { m_MemoryStack.SetAllocOwner( pszAllocOwner ); } + +private: + CMemoryStack m_MemoryStack; + int m_nAllocated; +}; + +#endif // MEMSTACK_H