/* * UNPUBLISHED -- Rights reserved under the copyright laws of the * United States. Use of a copyright notice is precautionary only and * does not imply publication or disclosure. * * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. */ /* * ZONE MEMORY MANAGER * * Goals: * 1. Minimize overhead * 2. Minimize fragmentation * * Constraints: * 1. Maximum allocated block size is 32MB * 2. Maximum 64 different memory tags supported * 3. Maximum 256 byte alignment * * All memory required by the manager is allocated at startup in * the form of one large pool. * * Allocated blocks require a 4 byte header to store size, tag, and * alignment information. Blocks that need to support the Z_TagFree() * feature require an additional 8 byte link list structure. * * Free blocks require a 16 bytes of tracking information. If possible * this information is stored directly in the block (which is in the * pool.) If the free block is not large enough, its information is * stored in an overflow buffer. * * In an effort to reduce fragmentation, blocks allocated for a short * period of time at the end of the pool. All other blocks are allocated * at the start. Allocation is first fit. * */ #include "../game/q_shared.h" #include "qcommon.h" #include "../renderer/qgl_console.h" #ifdef _GAMECUBE #include #endif #ifdef _WINDOWS #include #endif #ifdef _XBOX #include #endif // Where do hunk allocations go? static memtag_t hunk_tag; // Used to mark the start and end of blocks in debug mode #define ZONE_MAGIC 0xfe // Size of the free block overflow buffer #define ZONE_FREE_OVERFLOW 4096 // Indicates whether or not special (slow) debug code should be enabled #define ZONE_DEBUG 0 // Allocate all available memory minus this amount #ifdef _GAMECUBE #define ZONE_HEAP_FREE_DEBUG (64*1024*4) #define ZONE_HEAP_FREE_RELEASE (0) #ifdef _DEBUG # define ZONE_HEAP_FREE ZONE_HEAP_FREE_DEBUG #else # define ZONE_HEAP_FREE ZONE_HEAP_FREE_RELEASE #endif #else //Game needs about 8 MB for framebuffers audio, bink, etc., plus 24 MB //for textures. //# define ZONE_HEAP_FREE (1024*1024*8 + 24*1024*1024) # define ZONE_HEAP_FREE (1024*1024*8 + 24*1024*1024 + 4*1024*1024) #endif // Should we emulate the smaller memory footprint of actual release systems? #define ZONE_EMULATE_SPACE 0 // All standard header data is crammed into 4 bytes typedef unsigned int ZoneHeader; // Debug markers to check for overflow/underflow typedef unsigned int ZoneDebugHeader; typedef unsigned char ZoneDebugFooter; // Extended header information for memory freed with TagFree() struct ZoneLinkHeader { ZoneLinkHeader* m_Next; ZoneLinkHeader* m_Prev; }; static ZoneLinkHeader* s_LinkBase; // Free memory block tracking information struct ZoneFreeBlock { unsigned int m_Address; unsigned int m_Size; ZoneFreeBlock* m_Next; ZoneFreeBlock* m_Prev; }; // Buffer to hold free memory information that we can't // fit directly in the pool static ZoneFreeBlock s_FreeOverflow[ZONE_FREE_OVERFLOW]; static int s_LastOverflowIndex; static ZoneFreeBlock s_FreeStart; static ZoneFreeBlock s_FreeEnd; // Various stats collected at runtime struct ZoneStats { int m_CountAlloc; int m_SizeAlloc; int m_OverheadAlloc; int m_PeakAlloc; int m_CountFree; int m_SizeFree; int m_SizesPerTag[TAG_COUNT]; int m_CountsPerTag[TAG_COUNT]; }; static ZoneStats s_Stats; // Special empty block for zero size allocations struct ZoneEmptyBlock { ZoneHeader header; #ifdef _DEBUG ZoneDebugHeader start; ZoneDebugFooter end; #endif }; #ifdef _DEBUG static ZoneEmptyBlock s_EmptyBlock = {TAG_STATIC << 25, ZONE_MAGIC, ZONE_MAGIC}; #else static ZoneEmptyBlock s_EmptyBlock = {TAG_STATIC << 25}; #endif // Free block jump table for fast memory deallocation #define Z_JUMP_TABLE_SIZE 64 static ZoneFreeBlock* s_FreeJumpTable[Z_JUMP_TABLE_SIZE]; static unsigned int s_FreeJumpResolution; static void* s_PoolBase; static bool s_Initialized = false; static bool s_IsNewDeleteTemp = false; #ifndef _GAMECUBE static HANDLE s_Mutex = INVALID_HANDLE_VALUE; #endif static void Z_Stats_f(void); void Z_Details_f(void); void Z_DumpMemMap_f(void); #ifdef _XBOX void ShowOSMemory(void) { MEMORYSTATUS stat; GlobalMemoryStatus(&stat); Com_Printf(" total mem: %d, free mem: %d\n", stat.dwTotalPhys / 1024, stat.dwAvailPhys / 1024); FILE *out = fopen("d:\\osmem.txt", "a"); if(out) { fprintf(out, "total mem: %d, free mem: %d\n", stat.dwTotalPhys / 1024, stat.dwAvailPhys / 1024); fclose(out); } } #endif int Z_MemFree(void) { return s_Stats.m_SizeFree; } void Com_InitZoneMemory(void) { // assert(!s_Initialized); // Zone now initializes on first use, can't reliably assume anything here if (s_Initialized) return; Com_Printf("Initialising zone memory .....\n"); // Clear some globals memset(&s_Stats, 0, sizeof(s_Stats)); memset(s_FreeOverflow, 0, sizeof(s_FreeOverflow)); s_LastOverflowIndex = 0; s_LinkBase = NULL; s_IsNewDeleteTemp = false; // Alloc the pool #if defined(_XBOX) MEMORYSTATUS status; GlobalMemoryStatus(&status); // BTO : VVFIXME - Extra little note to see how much memory // is being used by globals/statics Com_Printf("*** PhysRAM: %d used, %d free\n", status.dwTotalPhys-status.dwAvailPhys, status.dwAvailPhys); SIZE_T size; # if ZONE_EMULATE_SPACE #ifdef _DEBUG //Emulated space is always about 6 megs off from release build. Try //to compensate. This number may need tweaking in the future. SIZE_T exe = 6500 * 1024; #else SIZE_T exe = 0; //Exe size is already reflected in GlobalMemoryStatus(). #endif size = 0x4000000 - (exe + ZONE_HEAP_FREE); # else size = status.dwAvailPhys - ZONE_HEAP_FREE; # endif s_PoolBase = GlobalAlloc(0, size); #elif defined(_WINDOWS) SIZE_T size = 50*1024*1024; s_PoolBase = GlobalAlloc(0, size); #endif // Setup the initial free block ZoneFreeBlock* base = (ZoneFreeBlock*)s_PoolBase; base->m_Address = (unsigned int)s_PoolBase; base->m_Size = size; base->m_Next = &s_FreeEnd; base->m_Prev = &s_FreeStart; // Init the free block jump table memset(s_FreeJumpTable, 0, Z_JUMP_TABLE_SIZE * sizeof(ZoneFreeBlock*)); s_FreeJumpResolution = (size / Z_JUMP_TABLE_SIZE) + 1; s_FreeJumpTable[0] = base; // Setup free block dummies s_FreeStart.m_Address = 0; s_FreeStart.m_Size = 0; s_FreeStart.m_Next = base; s_FreeStart.m_Prev = NULL; s_FreeEnd.m_Address = 0xFFFFFFFF; s_FreeEnd.m_Size = 0; s_FreeEnd.m_Next = NULL; s_FreeEnd.m_Prev = base; s_Stats.m_CountFree = 1; s_Stats.m_SizeFree = size; s_Initialized = true; // Add some commands Cmd_AddCommand("zone_stats", Z_Stats_f); Cmd_AddCommand("zone_details", Z_Details_f); Cmd_AddCommand("zone_memmap", Z_DumpMemMap_f); #ifndef _GAMECUBE s_Mutex = CreateMutex(NULL, FALSE, NULL); #endif } void Com_ShutdownZoneMemory(void) { assert(s_Initialized); // Remove commands Cmd_RemoveCommand("zone_stats"); Cmd_RemoveCommand("zone_details"); Cmd_RemoveCommand("zone_memmap"); if (s_Stats.m_CountAlloc) { // Free all memory // CM_ReleaseVisData(); Z_TagFree(TAG_ALL); } // Clear some globals memset(&s_Stats, 0, sizeof(s_Stats)); memset(s_FreeOverflow, 0, sizeof(s_FreeOverflow)); s_LastOverflowIndex = 0; s_LinkBase = NULL; // Free the pool #ifndef _GAMECUBE GlobalFree(s_PoolBase); CloseHandle(s_Mutex); #endif s_PoolBase = NULL; s_Initialized = false; } // Determine if a tag should only be allocated for a very // short period of time. static bool Z_IsTagTemp(memtag_t eTag) { return eTag == TAG_TEMP_WORKSPACE || #ifndef _JK2MP eTag == TAG_TEMP_SAVEGAME_WORKSPACE || eTag == TAG_STRING || eTag == TAG_GP2 || #endif eTag == TAG_SND_RAWDATA || eTag == TAG_ICARUS || #ifdef _JK2MP eTag == TAG_TEXTPOOL || eTag == TAG_TEMP_HUNKALLOC || #endif eTag == TAG_LISTFILES; } // Determine if a tag needs TagFree() support. static bool Z_IsTagLinked(memtag_t eTag) { return eTag == TAG_BSP || #ifndef _JK2MP eTag == TAG_HUNKALLOC || eTag == TAG_HUNKMISCMODELS || eTag == TAG_G_ALLOC || #endif #ifdef _JK2MP eTag == TAG_CG_UI_ALLOC || eTag == TAG_BG_ALLOC || eTag == TAG_HUNK_MARK1 || eTag == TAG_HUNK_MARK2 || eTag == TAG_TEMP_HUNKALLOC || #endif eTag == TAG_UI_ALLOC; } static int Z_CalcAlignmentPad(int iAlign, unsigned int iAddress, unsigned int iOffset, unsigned int iSize, unsigned int iHeaderSize, unsigned int iFooterSize) { int align_size; if (iAlign == 0) return 0; if (iOffset == 0) { // Align data at low end of block align_size = iAlign - ((iAddress + iHeaderSize) % iAlign); } else { // Align data at high end of block unsigned int block_start = iAddress + iOffset - iSize + iHeaderSize; align_size = block_start % iAlign; } if (align_size == iAlign) { return 0; } return align_size; } static ZoneFreeBlock* Z_GetOverflowBlock(void) { for (int i = s_LastOverflowIndex; i < ZONE_FREE_OVERFLOW; ++i) { if (s_FreeOverflow[i].m_Address == 0) { s_LastOverflowIndex = i; return &s_FreeOverflow[i]; } } for (int j = 0; j < s_LastOverflowIndex; ++j) { if (s_FreeOverflow[j].m_Address == 0) { s_LastOverflowIndex = j; return &s_FreeOverflow[j]; } } return NULL; } static inline bool Z_IsFreeBlockLargeEnough(ZoneFreeBlock* pBlock, int iSize, int iHeaderSize, int iFooterSize, int iAlign, bool bLow, int& iAlignPad) { // Is the block large enough? if (pBlock->m_Size >= iSize) { if (iAlign > 0) { // If we need some aligment, we need to check size // against that as well. iAlignPad = Z_CalcAlignmentPad(iAlign, pBlock->m_Address, !bLow ? pBlock->m_Size : 0, iSize, iHeaderSize, iFooterSize); if (pBlock->m_Size < iAlignPad + iSize) { return false; } } return true; } return false; } static ZoneFreeBlock* Z_FindFirstFree(int iSize, int iHeaderSize, int iFooterSize, int iAlign, int& iAlignPad) { for (ZoneFreeBlock* block = s_FreeStart.m_Next; block; block = block->m_Next) { if (Z_IsFreeBlockLargeEnough(block, iSize, iHeaderSize, iFooterSize, iAlign, true, iAlignPad)) { return block; } } return NULL; } static ZoneFreeBlock* Z_FindLastFree(int iSize, int iHeaderSize, int iFooterSize, int iAlign, int& iAlignPad) { for (ZoneFreeBlock* block = s_FreeEnd.m_Prev; block; block = block->m_Prev) { if (Z_IsFreeBlockLargeEnough(block, iSize, iHeaderSize, iFooterSize, iAlign, false, iAlignPad)) { return block; } } return NULL; } static bool Z_ValidateFree(void) { #if ZONE_DEBUG // Make sure no free blocks are overlapping for (ZoneFreeBlock* a = &s_FreeStart; a; a = a->m_Next) { if (a->m_Address == 0 && a->m_Size != 0) { return false; } for (ZoneFreeBlock* b = &s_FreeStart; b; b = b->m_Next) { if (a != b && a->m_Address >= b->m_Address && a->m_Address < b->m_Address + b->m_Size) { return false; } } } #endif return true; } static bool Z_ValidateLinks(void) { #if ZONE_DEBUG // Make sure links are sane for (ZoneLinkHeader* a = s_LinkBase; a; a = a->m_Next) { if ((a->m_Next && a != a->m_Next->m_Prev) || (a->m_Prev && a != a->m_Prev->m_Next)) { return false; } } #endif return true; } static int Z_GetJumpTableIndex(unsigned int iAddress) { int index = (iAddress - (unsigned int)s_PoolBase) / s_FreeJumpResolution; if (index < 0) return 0; if (index >= Z_JUMP_TABLE_SIZE) return Z_JUMP_TABLE_SIZE - 1; return index; } static ZoneFreeBlock* Z_GetFreeBlockBefore(unsigned int iAddress) { // Find this block's position in the jump table int index = Z_GetJumpTableIndex(iAddress) - 1; // Find a valid jump table entry while (index >= 0 && !s_FreeJumpTable[index]) --index; if (index < 0) return &s_FreeStart; return s_FreeJumpTable[index]; } static void Z_RemoveFromJumpTable(ZoneFreeBlock* pBlock) { // Is this block in the jump table? int index = Z_GetJumpTableIndex(pBlock->m_Address); if (s_FreeJumpTable[index] == pBlock) { // See if the next block will fit in our slot if (pBlock->m_Next != &s_FreeEnd) { int nindex = Z_GetJumpTableIndex(pBlock->m_Next->m_Address); if (nindex == index) { s_FreeJumpTable[index] = pBlock->m_Next; return; } } // See if the previous block will fit in our slot if (pBlock->m_Prev != &s_FreeStart) { int pindex = Z_GetJumpTableIndex(pBlock->m_Prev->m_Address); if (pindex == index) { s_FreeJumpTable[index] = pBlock->m_Prev; return; } } // No other free blocks fit here, give up s_FreeJumpTable[index] = NULL; } } static void Z_LinkFreeBlock(ZoneFreeBlock* pBlock) { ZoneFreeBlock* cur = Z_GetFreeBlockBefore(pBlock->m_Address); for (; cur; cur = cur->m_Next) { // Find the correct position, ordered by address if (cur->m_Address > pBlock->m_Address) { // Link up the block pBlock->m_Next = cur; pBlock->m_Prev = cur->m_Prev; cur->m_Prev->m_Next = pBlock; cur->m_Prev = pBlock; // Update the jump table if necessary int index = Z_GetJumpTableIndex(pBlock->m_Address); if (!s_FreeJumpTable[index]) { s_FreeJumpTable[index] = pBlock; } s_Stats.m_CountFree++; s_Stats.m_SizeFree += pBlock->m_Size; assert(Z_ValidateFree()); break; } } } static void* Z_SplitFree(ZoneFreeBlock* pBlock, int iSize, bool bLow) { assert(pBlock->m_Size >= iSize); Z_RemoveFromJumpTable(pBlock); // Delink the free block ZoneFreeBlock fblock = *pBlock; pBlock->m_Prev->m_Next = pBlock->m_Next; pBlock->m_Next->m_Prev = pBlock->m_Prev; pBlock->m_Address = 0; s_Stats.m_CountFree--; s_Stats.m_SizeFree -= pBlock->m_Size; assert(Z_ValidateFree()); if (fblock.m_Size > iSize) { // Split the block into an allocated and free portion int remainder = fblock.m_Size - iSize; if (remainder < sizeof(ZoneFreeBlock)) { // Free portion is not large to hold free info -- // we're going to have to use the overflow buffer. ZoneFreeBlock* nblock = Z_GetOverflowBlock(); if (nblock == NULL) { Z_Details_f(); Com_Error(ERR_FATAL, "Zone free overflow buffer overflowed!"); } // Split the block void* ret; if (bLow) { ret = (void*)fblock.m_Address; nblock->m_Address = fblock.m_Address + iSize; } else { ret = (void*)(fblock.m_Address + remainder); nblock->m_Address = fblock.m_Address; } nblock->m_Size = remainder; Z_LinkFreeBlock(nblock); return ret; } else { // Free portion is large enough -- split it void* ret; ZoneFreeBlock* nblock; if (bLow) { ret = (void*)fblock.m_Address; nblock = (ZoneFreeBlock*)(fblock.m_Address + iSize); } else { ret = (void*)(fblock.m_Address + remainder); nblock = (ZoneFreeBlock*)fblock.m_Address; } nblock->m_Address = (unsigned int)nblock; nblock->m_Size = remainder; Z_LinkFreeBlock(nblock); return ret; } } else { // No need to split, just return block. return (void*)fblock.m_Address; } } static void Z_SetupAlignmentPad(void* pBlock, int iAlignPad, bool bLow) { // Clear alignment bytes memset(pBlock, 0, iAlignPad); // If we have more than 1 alignment byte, the first align byte // tells us how many additional bytes we have. if (iAlignPad > 1) { assert(iAlignPad < 256); unsigned char* ptr; if (bLow) { ptr = (unsigned char*)pBlock + (iAlignPad - 1); } else { ptr = (unsigned char*)pBlock; } *ptr = iAlignPad - 1; } } void Z_MallocFail(const char* pMessage, int iSize, memtag_t eTag) { // Report the error // Com_Printf("Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag); Com_Printf("Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag); Z_Details_f(); Z_DumpMemMap_f(); // Com_Printf("(Repeat): Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag); Com_Printf("(Repeat): Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag); // Clear the screen blue to indicate out of memory for (;;) { qglBeginFrame(); qglClearColor(0, 0, 1, 1); qglClear(GL_COLOR_BUFFER_BIT); qglEndFrame(); } } void *Z_Malloc(int iSize, memtag_t eTag, qboolean bZeroit, int iAlign) { // assert(s_Initialized); // Zone now initializes on first use. (During static constructors) if (!s_Initialized) Com_InitZoneMemory(); if (iSize == 0) { #ifdef _DEBUG return (void*)(&s_EmptyBlock.start + 1); #else return (void*)(&s_EmptyBlock.header + 1); #endif } if (iSize < 0) { Z_MallocFail("Negative size", iSize, eTag); return NULL; } #ifndef _GAMECUBE WaitForSingleObject(s_Mutex, INFINITE); #endif // Make new/delete memory temporary if requested if (eTag == TAG_NEWDEL && s_IsNewDeleteTemp) { eTag = TAG_TEMP_WORKSPACE; } // Determine how much space we need with headers and footers int header_size = sizeof(ZoneHeader); int footer_size = 0; if (Z_IsTagLinked(eTag)) { header_size += sizeof(ZoneLinkHeader); } #ifdef _DEBUG header_size += sizeof(ZoneDebugHeader); footer_size += sizeof(ZoneDebugFooter); #endif int real_size = iSize + header_size + footer_size; int align_pad = 0; // Get a bit of free memory. Temporary memory is allocated // from the end. More permanent allocations are done at the // begining of the pool. ZoneFreeBlock* fblock; if (Z_IsTagTemp(eTag)) { fblock = Z_FindLastFree(real_size, header_size, footer_size, iAlign, align_pad); } else { fblock = Z_FindFirstFree(real_size, header_size, footer_size, iAlign, align_pad); } // Did we actually find some memory? if (!fblock) { #ifndef _GAMECUBE ReleaseMutex(s_Mutex); #endif // if(eTag == TAG_TEMP_SND_RAWDATA) { if(eTag == TAG_SND_RAWDATA) { return NULL; } Z_MallocFail("Out of memory", iSize, eTag); return NULL; } // Add any alignment bytes real_size += align_pad; // Split the free block and get a pointer to the start // allocated space. void* ablock; if (Z_IsTagTemp(eTag)) { ablock = Z_SplitFree(fblock, real_size, false); // Append align pad to end of block Z_SetupAlignmentPad( (void*)((char*)ablock + real_size - align_pad), align_pad, false); } else { ablock = Z_SplitFree(fblock, real_size, true); // Insert align pad at block start Z_SetupAlignmentPad(ablock, align_pad, true); ablock = (void*)((char*)ablock + align_pad); } if (!ablock) { Z_MallocFail("Failed to split", iSize, eTag); } // Add linking header if necessary if (Z_IsTagLinked(eTag)) { ZoneLinkHeader* linked = (ZoneLinkHeader*)ablock; linked->m_Next = s_LinkBase; linked->m_Prev = NULL; if (s_LinkBase) { s_LinkBase->m_Prev = linked; } s_LinkBase = linked; assert(Z_ValidateLinks()); // Next... ablock = (void*)((char*)ablock + sizeof(ZoneLinkHeader)); } // Setup the header: // 31 - alignment flag // 25-30 - tag // 0-24 - size without headers/footers assert(iSize >= 0 && iSize < (1 << 25)); assert(eTag >= 0 && eTag < 64); ZoneHeader* header = (ZoneHeader*)ablock; *header = (((unsigned int)eTag) << 25) | ((unsigned int)iSize); if (align_pad) { *header |= (1 << 31); } // Next... ablock = (void*)((char*)ablock + sizeof(ZoneHeader)); #ifdef _DEBUG { // Setup the debug markers ZoneDebugHeader* debug_header = (ZoneDebugHeader*)ablock; ZoneDebugFooter* debug_footer = (ZoneDebugFooter*)((char*)debug_header + (sizeof(ZoneDebugHeader) + iSize)); *debug_header = ZONE_MAGIC; *debug_footer = ZONE_MAGIC; // Next... ablock = (void*)((char*)ablock + sizeof(ZoneDebugHeader)); } #endif // Update the stats s_Stats.m_SizeAlloc += iSize; s_Stats.m_OverheadAlloc += header_size + footer_size + align_pad; s_Stats.m_SizesPerTag[eTag] += iSize; s_Stats.m_CountAlloc++; s_Stats.m_CountsPerTag[eTag]++; if (s_Stats.m_SizeAlloc + s_Stats.m_OverheadAlloc > s_Stats.m_PeakAlloc) { s_Stats.m_PeakAlloc = s_Stats.m_SizeAlloc + s_Stats.m_OverheadAlloc; } // Return a pointer to data memory if (bZeroit) { memset(ablock, 0, iSize); } assert(iAlign == 0 || (unsigned int)ablock % iAlign == 0); /* This is useful for figuring out who's allocating a certain block of memory. Please don't remove it. if(eTag == TAG_NEWDEL && (unsigned int)ablock >= 0x806c0000 && (unsigned int)ablock <= 0x806c1000 && iSize == 24) { int suck = 0; } if(eTag == TAG_SMALL && (iSize == 7 || iSize == 96)) { int suck = 0; } if(eTag == TAG_CLIENTS) { int suck = 0; } */ #ifndef _GAMECUBE ReleaseMutex(s_Mutex); #endif return ablock; } static memtag_t Z_GetTag(const ZoneHeader* header) { return (*header & 0x7E000000) >> 25; } static unsigned int Z_GetSize(const ZoneHeader* header) { return *header & 0x1FFFFFF; } static int Z_GetAlign(const ZoneHeader* header) { if (*header & (1 << 31)) { unsigned char* ptr = (unsigned char*)header; memtag_t tag = Z_GetTag(header); // point to the first alignment block if (Z_IsTagTemp(tag)) { ptr += sizeof(ZoneHeader) + Z_GetSize(header); #ifdef _DEBUG ptr += sizeof(ZoneDebugHeader) + sizeof(ZoneDebugFooter); #endif } else { if (Z_IsTagLinked(tag)) { // skip the link header ptr -= sizeof(ZoneLinkHeader); } ptr -= 1; } return *ptr + 1; } return 0; } int Z_Size(void *pvAddress) { assert(s_Initialized); #ifdef _DEBUG ZoneDebugHeader* debug = (ZoneDebugHeader*)pvAddress - 1; if (*debug != ZONE_MAGIC) { Com_Error(ERR_FATAL, "Z_Size(): Not a valid zone header!"); return 0; // won't get here } pvAddress = (void*)debug; #endif ZoneHeader* header = (ZoneHeader*)pvAddress - 1; if (Z_GetTag(header) == TAG_STATIC) { return 0; // kind of } return Z_GetSize(header); } static void Z_Coalasce(ZoneFreeBlock* pBlock) { unsigned int size = 0; // Find later free blocks adjacent to us ZoneFreeBlock* end; for (end = pBlock->m_Next; end->m_Next; end = end->m_Next) { if (end->m_Address != end->m_Prev->m_Address + end->m_Prev->m_Size) { break; } size += end->m_Size; Z_RemoveFromJumpTable(end); end->m_Address = 0; // invalidate block s_Stats.m_CountFree--; } // Find previous free blocks adjacent to us ZoneFreeBlock* start; for (start = pBlock; start->m_Prev; start = start->m_Prev) { if (start->m_Prev->m_Address + start->m_Prev->m_Size != start->m_Address) { break; } size += start->m_Size; Z_RemoveFromJumpTable(start); start->m_Address = 0; // invalidate block s_Stats.m_CountFree--; } // Do we need to coalesce some blocks? if (start->m_Next != end) { start->m_Next = end; end->m_Prev = start; start->m_Size += size; } } // Return type of Z_Free differs in SP/MP. Macro hack to wrap it up #ifdef _JK2MP void Z_Free(void *pvAddress) #define Z_FREE_RETURN(x) return #else int Z_Free(void *pvAddress) #define Z_FREE_RETURN(x) return (x) #endif { #ifdef _WINDOWS if (!s_Initialized) return; #endif assert(s_Initialized); #ifdef _DEBUG // check the header magic ZoneDebugHeader* debug_header = (ZoneDebugHeader*)pvAddress - 1; if (*debug_header != ZONE_MAGIC) { Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone header!"); Z_FREE_RETURN( 0 ); } ZoneHeader* header = (ZoneHeader*)debug_header - 1; // check the footer magic ZoneDebugFooter* debug_footer = (ZoneDebugFooter*)((char*)pvAddress + Z_GetSize(header)); if (*debug_footer != ZONE_MAGIC) { Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone footer!"); Z_FREE_RETURN( 0 ); } #else ZoneHeader* header = (ZoneHeader*)pvAddress - 1; #endif memtag_t tag = Z_GetTag(header); if (tag != TAG_STATIC) { #ifndef _GAMECUBE WaitForSingleObject(s_Mutex, INFINITE); #endif // Determine size of header and footer int header_size = sizeof(ZoneHeader); int align_size = Z_GetAlign(header); int footer_size = 0; int data_size = Z_GetSize(header); if (Z_IsTagLinked(tag)) { header_size += sizeof(ZoneLinkHeader); } if (Z_IsTagTemp(tag)) { footer_size += align_size; } else { header_size += align_size; } #ifdef _DEBUG header_size += sizeof(ZoneDebugHeader); footer_size += sizeof(ZoneDebugFooter); #endif int real_size = data_size + header_size + footer_size; // Update the stats s_Stats.m_SizeAlloc -= data_size; s_Stats.m_OverheadAlloc -= header_size + footer_size; s_Stats.m_SizesPerTag[tag] -= data_size; s_Stats.m_CountAlloc--; s_Stats.m_CountsPerTag[tag]--; // Delink block if (Z_IsTagLinked(tag)) { ZoneLinkHeader* linked = (ZoneLinkHeader*)header - 1; if (linked == s_LinkBase) { s_LinkBase = linked->m_Next; if (s_LinkBase) { s_LinkBase->m_Prev = NULL; } } else { if (linked->m_Next) { linked->m_Next->m_Prev = linked->m_Prev; } linked->m_Prev->m_Next = linked->m_Next; } assert(Z_ValidateLinks()); } // Clear the block header for safety *header = 0; // Add block to free list ZoneFreeBlock* nblock = NULL; if (real_size < sizeof(ZoneFreeBlock)) { // Not enough space in block to put free information -- // use overflow buffer. nblock = Z_GetOverflowBlock(); if (nblock == NULL) { Z_Details_f(); Com_Error(ERR_FATAL, "Zone free overflow buffer overflowed!"); } } else { // Place free information in block nblock = (ZoneFreeBlock*)((char*)pvAddress - header_size); } nblock->m_Address = (unsigned int)pvAddress - header_size; nblock->m_Size = real_size; Z_LinkFreeBlock(nblock); // Coalesce any adjacent free blocks Z_Coalasce(nblock); #ifndef _GAMECUBE ReleaseMutex(s_Mutex); #endif } Z_FREE_RETURN( 0 ); } int Z_MemSize(memtag_t eTag) { return s_Stats.m_SizesPerTag[eTag]; } #if ZONE_DEBUG void Z_FindLeak(void) { assert(s_Initialized); static int cycle_count = 0; const memtag_t tag = TAG_NEWDEL; struct PointerInfo { void* data; int counter; bool mark; }; const int max_pointers = 32768; static PointerInfo pointers[max_pointers]; static int num_pointers = 0; // Clear pointer existance for (int i = 0; i < num_pointers; ++i) { pointers[i].mark = false; } // Add all known pointers int start_num = num_pointers; for (ZoneLinkHeader* link = s_LinkBase; link;) { ZoneHeader* header = (ZoneHeader*)(link + 1); link = link->m_Next; if (Z_GetTag(header) == tag) { // See if the pointer already is in the array bool found = false; for (int k = start_num; k < num_pointers; ++k) { if (pointers[k].data == header) { ++pointers[k].counter; pointers[k].mark = true; found = true; break; } } // If the pointer is not in the array, add it if (!found) { assert(num_pointers < max_pointers); pointers[num_pointers].data = header; pointers[num_pointers].counter = 0; pointers[num_pointers].mark = true; ++num_pointers; } } } // Remove pointers that are no longer used for (int j = 0; j < num_pointers; ++j) { if (pointers[j].mark) { if (pointers[j].counter != cycle_count && pointers[j].counter != cycle_count - 1 && pointers[j].counter != 0) { Com_Printf("Memory leak: %p\n", pointers[j].data); } } else { int k; for (k = j; k < num_pointers; ++k) { if (pointers[k].mark) break; } if (k == num_pointers) break; memmove(pointers + j, pointers + k, (num_pointers - k) * sizeof(PointerInfo)); num_pointers -= k - j; } } ++cycle_count; } #endif void Z_TagPointers(memtag_t eTag) { assert(s_Initialized); #ifndef _GAMECUBE WaitForSingleObject(s_Mutex, INFINITE); #endif Com_Printf("Pointers for tag %d:\n", eTag); for (ZoneLinkHeader* link = s_LinkBase; link;) { ZoneHeader* header = (ZoneHeader*)(link + 1); link = link->m_Next; if (eTag == TAG_ALL || Z_GetTag(header) == eTag) { #ifdef _DEBUG Com_Printf("%x - %d\n", ((void*)((char*)header + sizeof(ZoneHeader) + sizeof(ZoneDebugHeader))), Z_Size(((void*)((char*)header + sizeof(ZoneHeader) + sizeof(ZoneDebugHeader))))); #else Com_Printf("%x - %d\n", (void*)(header + 1), Z_Size((void*)(header + 1))); #endif } } #ifndef _GAMECUBE ReleaseMutex(s_Mutex); #endif } void Z_TagFree(memtag_t eTag) { assert(s_Initialized); for (ZoneLinkHeader* link = s_LinkBase; link;) { ZoneHeader* header = (ZoneHeader*)(link + 1); link = link->m_Next; if (eTag == TAG_ALL || Z_GetTag(header) == eTag) { #ifdef _DEBUG Z_Free((void*)((char*)header + sizeof(ZoneHeader) + sizeof(ZoneDebugHeader))); #else Z_Free((void*)(header + 1)); #endif } } } void Z_SetNewDeleteTemporary(bool bTemp) { // Catch nested uses that break when unwinding the stack assert(bTemp != s_IsNewDeleteTemp); s_IsNewDeleteTemp = bTemp; } void *S_Malloc( int iSize ) { return Z_Malloc(iSize, TAG_SMALL, qfalse, 0); } int Z_GetLevelMemory(void) { #ifdef _JK2MP return s_Stats.m_SizesPerTag[TAG_BSP]; #else return s_Stats.m_SizesPerTag[TAG_HUNKALLOC] + s_Stats.m_SizesPerTag[TAG_HUNKMISCMODELS] + s_Stats.m_SizesPerTag[TAG_BSP]; #endif } #ifdef _JK2MP int Z_GetHunkMemory(void) { return s_Stats.m_SizesPerTag[TAG_HUNK_MARK1] + s_Stats.m_SizesPerTag[TAG_HUNK_MARK2] + s_Stats.m_SizesPerTag[TAG_TEMP_HUNKALLOC]; } #endif int Z_GetTerrainMemory(void) { return s_Stats.m_SizesPerTag[TAG_CM_TERRAIN] + s_Stats.m_SizesPerTag[TAG_CM_TERRAIN_TEMP] + #ifdef _JK2MP s_Stats.m_SizesPerTag[TAG_TERRAIN] + #endif s_Stats.m_SizesPerTag[TAG_R_TERRAIN]; } int Z_GetMiscMemory(void) { return s_Stats.m_SizeAlloc - (Z_GetLevelMemory() + #ifdef _JK2MP Z_GetHunkMemory() + #endif Z_GetTerrainMemory() + s_Stats.m_SizesPerTag[TAG_MODEL_GLM] + s_Stats.m_SizesPerTag[TAG_MODEL_GLA] + s_Stats.m_SizesPerTag[TAG_MODEL_MD3] + s_Stats.m_SizesPerTag[TAG_BINK] + s_Stats.m_SizesPerTag[TAG_SND_RAWDATA]); } #ifdef _GAMECUBE static int texMemSize = 0; #else extern int texMemSize; #endif void Z_CompactStats(void) { assert(s_Initialized); //This report is conservative. Divides by 1000 instead of 1024 and //then rounds up. Sys_Log("memory-map.txt", va("**Z_CompactStats Start**\n")); Sys_Log("memory-map.txt", va("map: %s\n", Cvar_VariableString( "mapname" )) ); Sys_Log("memory-map.txt", va("OV: %d, LVL: %d, GLM: %d, GLA: %d, MD3: %d\n", (s_Stats.m_OverheadAlloc / 1000) + 1, (Z_GetLevelMemory() / 1000) + 1, (s_Stats.m_SizesPerTag[TAG_MODEL_GLM] / 1000) + 1, (s_Stats.m_SizesPerTag[TAG_MODEL_GLA] / 1000) + 1, (s_Stats.m_SizesPerTag[TAG_MODEL_MD3] / 1000) + 1)); Sys_Log("memory-map.txt", va("TER: %d, SND: %d, TEX: %d, FMV: %d, MSC: %d\n", (Z_GetTerrainMemory() / 1000) + 1, (s_Stats.m_SizesPerTag[TAG_SND_RAWDATA] / 1000) + 1, (texMemSize / 1000) + 1, (s_Stats.m_SizesPerTag[TAG_BINK] / 1000) + 1, (Z_GetMiscMemory() / 1000) + 1)); #ifdef _JK2MP Sys_Log("memory-map.txt", va("HUNK: %d, THUNK: %d\n", ((s_Stats.m_SizesPerTag[TAG_HUNK_MARK1] + s_Stats.m_SizesPerTag[TAG_HUNK_MARK2]) / 1000) + 1, (s_Stats.m_SizesPerTag[TAG_TEMP_HUNKALLOC] / 1000) + 1)); #endif Sys_Log("memory-map.txt", va("Free Zone: %d\n", s_Stats.m_SizeFree)); } static void Z_Stats_f(void) { assert(s_Initialized); // Display some memory usage summary information... Com_Printf("\nThe zone is using %d bytes (%.2fMB) in %d memory blocks\n", s_Stats.m_SizeAlloc, (float)s_Stats.m_SizeAlloc / 1024.0f / 1024.0f, s_Stats.m_CountAlloc); Com_Printf("Free memory is %d bytes (%.2fMB) in %d memory blocks\n", s_Stats.m_SizeFree, (float)s_Stats.m_SizeFree / 1024.0f / 1024.0f, s_Stats.m_CountFree); Com_Printf("The zone peaked at %d bytes (%.2fMB)\n", s_Stats.m_PeakAlloc, (float)s_Stats.m_PeakAlloc / 1024.0f / 1024.0f); Com_Printf("The zone overhead is %d bytes (%.2fMB)\n", s_Stats.m_OverheadAlloc, (float)s_Stats.m_OverheadAlloc / 1024.0f / 1024.0f); } void Z_Details_f(void) { assert(s_Initialized); // Display some tag specific information... Com_Printf("---------------------------------------------------------------------------\n"); Com_Printf("%20s %9s\n","Zone Tag","Bytes"); Com_Printf("%20s %9s\n","--------","-----"); for (int i=0; im_Next) { while (fblock->m_Address > cur + 1024) { WRITECHAR("*"); } if (fblock->m_Address > cur && fblock->m_Address < cur + 1024) { WRITECHAR("+"); } while (fblock->m_Address + fblock->m_Size > cur + 1024) { WRITECHAR("-"); } if (fblock->m_Address + fblock->m_Size > cur && fblock->m_Address + fblock->m_Size < cur + 1024) { WRITECHAR("+"); } } Sys_Log("memmap.txt", "\n"); } void Z_DisplayLevelMemory(int size, int surf, int block) { Z_DumpMemMap_f(); //Yes, it should be divided by 1024, but I'm going for a safety margin //by rounding down. //Com_Printf("level memory used: %d KB\n", size / 1000); //Z_CompactStats(size, surf, block); Z_CompactStats(); } void Z_DisplayLevelMemory(void) { #ifdef _GAMECUBE extern void R_SurfMramUsed(int &surface, int &block); int surface, block; R_SurfMramUsed(surface, block); Z_DisplayLevelMemory(Z_GetLevelMemory(), surface, block); #else Z_DisplayLevelMemory(Z_GetLevelMemory(), 0, 0); #endif } /* ======================== CopyString NOTE: never write over the memory CopyString returns because memory from a memstatic_t might be returned ======================== */ char *CopyString( const char *in ) { struct ZoneSingleChar { ZoneHeader header; #ifdef _DEBUG ZoneDebugHeader start; #endif char data[2]; #ifdef _DEBUG ZoneDebugFooter end; #endif }; #ifdef _DEBUG static ZoneSingleChar empty = {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "\0", ZONE_MAGIC}; static ZoneSingleChar numbers[10] = { {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "0", ZONE_MAGIC}, {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "1", ZONE_MAGIC}, {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "2", ZONE_MAGIC}, {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "3", ZONE_MAGIC}, {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "4", ZONE_MAGIC}, {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "5", ZONE_MAGIC}, {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "6", ZONE_MAGIC}, {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "7", ZONE_MAGIC}, {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "8", ZONE_MAGIC}, {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "9", ZONE_MAGIC}, }; #else static ZoneSingleChar empty = {(TAG_STATIC << 25) | 2, "\0"}; static ZoneSingleChar numbers[10] = { {(TAG_STATIC << 25) | 2, "0"}, {(TAG_STATIC << 25) | 2, "1"}, {(TAG_STATIC << 25) | 2, "2"}, {(TAG_STATIC << 25) | 2, "3"}, {(TAG_STATIC << 25) | 2, "4"}, {(TAG_STATIC << 25) | 2, "5"}, {(TAG_STATIC << 25) | 2, "6"}, {(TAG_STATIC << 25) | 2, "7"}, {(TAG_STATIC << 25) | 2, "8"}, {(TAG_STATIC << 25) | 2, "9"}, }; #endif char *out; if (!in[0]) { return empty.data; } else if (!in[1]) { if (in[0] >= '0' && in[0] <= '9') { return numbers[in[0]-'0'].data; } } out = (char *) S_Malloc (strlen(in)+1); strcpy (out, in); // Z_Label(out,in); return out; } void Com_TouchMemory(void) { // Stub function. Do nothing. return; } qboolean Z_IsFromZone(void *pvAddress, memtag_t eTag) { assert(s_Initialized); #ifdef _DEBUG ZoneDebugHeader* debug = (ZoneDebugHeader*)pvAddress - 1; if (*debug != ZONE_MAGIC) { return qfalse; } pvAddress = (void*)debug; #endif ZoneHeader* header = (ZoneHeader*)pvAddress - 1; if (Z_GetTag(header) != eTag) { return qfalse; } return Z_GetSize(header); } /* Hunk emulation - PC switched to system similar to ours. I made the remaining changes so that the two are identical. */ #ifdef _JK2MP qboolean Com_TheHunkMarkHasBeenMade(void) { if (hunk_tag == TAG_HUNK_MARK2) { return qtrue; } return qfalse; } /* ================= Com_InitHunkMemory ================= */ void Com_InitHunkMemory(void) { hunk_tag = TAG_HUNK_MARK1; Hunk_Clear(); } /* ==================== Hunk_MemoryRemaining ==================== */ int Hunk_MemoryRemaining(void) { return 0; } /* =================== Hunk_SetMark The server calls this after the level and game VM have been loaded =================== */ void Hunk_SetMark(void) { hunk_tag = TAG_HUNK_MARK2; } /* ================= Hunk_ClearToMark The client calls this before starting a vid_restart or snd_restart ================= */ void Hunk_ClearToMark(void) { assert(hunk_tag == TAG_HUNK_MARK2); //if this is not true then no mark has been made Z_TagFree(TAG_HUNK_MARK2); } /* ================= Hunk_CheckMark ================= */ qboolean Hunk_CheckMark( void ) { if (hunk_tag != TAG_HUNK_MARK1) { return qtrue; } return qfalse; } /* ================= Hunk_Clear The server calls this before shutting down or loading a new map VVFIXME - PC version does lots of other things in here. ================= */ void R_HunkClearCrap(void); void Hunk_Clear(void) { hunk_tag = TAG_HUNK_MARK1; Z_TagFree(TAG_HUNK_MARK1); Z_TagFree(TAG_HUNK_MARK2); R_HunkClearCrap(); /* Z_TagFree(TAG_HUNKALLOC); Z_TagFree(TAG_BSP_HUNK); Z_TagFree(TAG_BOT_HUNK); Z_TagFree(TAG_RENDERER_HUNK); Z_TagFree(TAG_SKELETON); Z_TagFree(TAG_MODEL_OTHER); Z_TagFree(TAG_MODEL_CHAR); VM_Clear(); */ } /* ================= Hunk_Alloc Allocate permanent (until the hunk is cleared) memory ================= */ void *Hunk_Alloc(int size, ha_pref preference) { return Z_Malloc(size, hunk_tag, qtrue); } /* ================= Hunk_AllocateTempMemory This is used by the file loading system. Multiple files can be loaded in temporary memory. When the files-in-use count reaches zero, all temp memory will be deleted ================= */ void *Hunk_AllocateTempMemory(int size) { // don't bother clearing, because we are going to load a file over it return Z_Malloc(size, TAG_TEMP_HUNKALLOC, qfalse); } /* ================== Hunk_FreeTempMemory ================== */ void Hunk_FreeTempMemory(void *buf) { Z_Free(buf); } /* ================= Hunk_ClearTempMemory The temp space is no longer needed. If we have left more touched but unused memory on this side, have future permanent allocs use this side. ================= */ void Hunk_ClearTempMemory(void) { Z_TagFree(TAG_TEMP_HUNKALLOC); } #endif // _JK2MP /* XTL Replacement functions XMemAlloc XMemFree XMemSize Replacing these lets us intercept ALL memory allocation done by the XTL, and lets the Zone take pretty much all available memory at startup */ /* This still doesn't work. Numrous allocations still use internal functions, so there's little benefit right now. XBOXAPI LPVOID WINAPI XMemAlloc(SIZE_T dwSize, DWORD dwAllocAttributes) { // We always give XTL 16 byte aligned memory return Z_Malloc(dwSize, TAG_XTL, ((PXALLOC_ATTRIBUTES)&dwAllocAttributes)->dwZeroInitialize, 16); } XBOXAPI VOID WINAPI XMemFree(PVOID pAddress, DWORD dwAllocAttributes) { Z_Free(pAddress); } XBOXAPI SIZE_T WINAPI XMemSize(PVOID pAddress, DWORD dwAllocAttributes) { return Z_Size(pAddress); } */