//===========================================================================// // File: memblock.cc // // Project: MUNGA Brick: Memory Manager // // Contents: Implementation details of the m_blockMemory block class // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // 10/20/94 JMA Initial coding. // // 10/28/94 JMA Made compatible with SGI CC // //---------------------------------------------------------------------------// // Copyright (C) 1994-1995, Virtual World Entertainment, Inc. // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// #include "StuffHeaders.hpp" //#define MEMORY_VERIFY //#define MEMORY_BLOCK_VERIFY //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MemoryBlockBase ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MemoryBlockBase* MemoryBlockBase::s_firstBlock = NULL; // //############################################################################# // // This function constructs a MemoryBlockBase variable, allocating a block of // m_blockMemory from heap, preparing it for suballocation of a constant record // m_size. // // NOTE: Record m_size must be large enough to contain a void pointer in order // to properly set up the re-use chain! // // Rec_Size - m_size in bytes of the suballocation unit // Start - number of records to allocate initially // Delta - number of records to allocate when growing the m_blockMemory block // //############################################################################# // MemoryBlockBase::MemoryBlockBase( size_t rec_size, size_t start, size_t delta, const char* name, HGOSHEAP parent ) { // //----------------------------------------------------------------- // Make sure that the requested record m_size is large enough if // debugging is enabled //----------------------------------------------------------------- // Verify(rec_size >= sizeof(MemoryBlockHeader)); // //------------------------------------------------------------------------- // Set up the m_size variables for the m_blockMemory block, and figure out // the byte sizes of the initial block and delta blocks //------------------------------------------------------------------------- // Verify(parent != g_Heap); m_heap = gos_CreateMemoryHeap(const_cast(name), 0, parent); m_recordSize = (rec_size+3)&~3; m_size = ((start * m_recordSize + sizeof(MemoryBlockHeader) + 0xFFF) & ~0xFFF) - sizeof(MemoryBlockHeader); m_growthSize = ((delta * m_recordSize + sizeof(MemoryBlockHeader) + 0xFFF) & ~0xFFF) - sizeof(MemoryBlockHeader); m_name = name; // //------------------------------------------------------------------------- // Allocate a block big enough for the requested m_blockMemory plus a link to // the next m_blockMemory block, initializing this link to NULL //------------------------------------------------------------------------- // #if !defined(MEMORY_VERIFY) m_blockMemory = Cast_Pointer( MemoryBlockHeader*, new(m_heap) BYTE[sizeof(MemoryBlockHeader) + m_size] ); Check_Object(m_blockMemory); m_blockMemory->m_next = NULL; m_blockMemory->m_size = m_size; // //-------------------------------------------------------------------- // Establish the beginning of the m_firstHeaderRecord record block, and // point the m_freeRecord record pointer to the beginning. There are no // m_deletedRecord records yet, so make sure Deleted is NULL //-------------------------------------------------------------------- // m_firstHeaderRecord = Cast_Pointer(BYTE*, m_blockMemory + 1); Check_Pointer(m_firstHeaderRecord); m_freeRecord = m_firstHeaderRecord; m_deletedRecord = NULL; #endif // //-------------------------------------------------------------------------- // If this is the first memory block, set the first block pointer and // initialize the ring. If not, insert the new block at the end of the ring //-------------------------------------------------------------------------- // if (!s_firstBlock) { s_firstBlock = m_next = m_previous = this; } else { MemoryBlockBase *block = s_firstBlock; Check_Object(block); block = block->m_previous; Check_Object(block); m_next = s_firstBlock; m_previous = block; block->m_next = this; s_firstBlock->m_previous = this; } } // //############################################################################# // // This function destroys the MemoryBlockBase object, deleting any additional // record blocks which were allocated // //############################################################################# // MemoryBlockBase::~MemoryBlockBase() { #if !defined(MEMORY_VERIFY) // //------------------------------------------------------------------------ // Find the address of the first record block, then delete blocks until we // come to the end of the chain //------------------------------------------------------------------------ // Check_Object(this); MemoryBlockHeader *block = m_blockMemory; while (block) { // //--------------------------------------------------------------------- // Save the address of the next m_blockMemory block, then delete this one // and get ready to delete the next one //--------------------------------------------------------------------- // Check_Object(block); MemoryBlockHeader *next_block = block->m_next; delete block; block = next_block; } #endif // //------------------------------------------------------------------------ // Remove the block from the ring. If the block is the first one, set the // first block pointer correctly. Note that we memoryblocks, as static // objects, should deconstruct in the opposite order they constructed, so // when we reach the first block, it should be the only one in the queue. //------------------------------------------------------------------------ // if (s_firstBlock == this) { if (m_next == this) { s_firstBlock = NULL; } else { s_firstBlock = m_next; goto Unlink; } } else { Unlink: MemoryBlockBase *next = m_next; Check_Object(next); MemoryBlockBase *prev = m_previous; Check_Object(prev); next->m_previous = prev; prev->m_next = next; } } // //############################################################################# // // This function allocates a fixed m_size record from the record blocks // //############################################################################# // void* MemoryBlockBase::Grow() { #if defined(MEMORY_VERIFY) STOP(("MemoryBlockBase::Grow() not available!\n")); #endif // //------------------------------------------------------------------------ // If we have m_freeRecord space left in the m_firstHeaderRecord record block, // allocate the new record from here, updating the next m_freeRecord record // pointer //------------------------------------------------------------------------ // Check_Object(this); if (m_freeRecord - m_firstHeaderRecord <= m_size - m_recordSize) { void *result = m_freeRecord; m_freeRecord += m_recordSize; return result; } // //------------------------------------------------------------------------- // Allocate a new block of records using the growth m_size, and make the // link field of the m_firstHeaderRecord block point to the new block. Then // make the new block the m_firstHeaderRecord block, and make its next link // NULL, as it is the end of the chain //------------------------------------------------------------------------- // m_size = m_growthSize; m_firstHeaderRecord -= sizeof(MemoryBlockHeader); BYTE *new_block = new(m_heap) BYTE[m_size + sizeof(MemoryBlockHeader)]; Check_Pointer(new_block); MemoryBlockHeader *header = Cast_Pointer(MemoryBlockHeader*, m_firstHeaderRecord); Check_Object(header); header->m_next = Cast_Pointer(MemoryBlockHeader*, new_block); Check_Object(header->m_next); m_firstHeaderRecord = new_block; header = Cast_Pointer(MemoryBlockHeader*, m_firstHeaderRecord); Check_Object(header); header->m_next = NULL; header->m_size = m_growthSize; // //-------------------------------------------------------------------------- // Make m_firstHeaderRecord point to the first available address in the new // block (having skipped the first field allocated to the link pointer), and // allocate this first block to the caller. Update the m_freeRecord pointer // to reflect this allocation //-------------------------------------------------------------------------- // m_firstHeaderRecord += sizeof(MemoryBlockHeader); m_freeRecord = m_firstHeaderRecord + m_recordSize; return m_firstHeaderRecord; } // //############################################################################# //############################################################################# // void MemoryBlockBase::UsageReport() { #if !defined(MEMORY_VERIFY) SPEW((GROUP_STUFF_MEMORY, " #Bytes #Rcds #Dels #Free Use% Name")); SPEW((GROUP_STUFF_MEMORY, "------- ------ ------ ------ ---- -------------------------------")); MemoryBlockBase *block = s_firstBlock; do { Check_Object(block); MemoryBlockHeader *header = block->m_blockMemory; size_t byte_count = 0; size_t record_count = 0; size_t deletion_count = 0; size_t unused_count = block->m_size - (block->m_freeRecord - block->m_firstHeaderRecord); unused_count /= block->m_recordSize; while (header) { Check_Object(header); record_count += header->m_size / block->m_recordSize; byte_count += header->m_size + sizeof(MemoryBlockHeader); header = header->m_next; } BYTE *deletion = block->m_deletedRecord; while (deletion) { ++deletion_count; deletion = *Cast_Pointer(BYTE**, deletion); } record_count -= deletion_count + unused_count; if( record_count!=0 ) { SPEW(( GROUP_STUFF_MEMORY, "%7d%7d%7d%7d%4d%%+", byte_count, record_count, deletion_count, unused_count, static_cast((1.0f - (deletion_count+unused_count)/(float)record_count)*100.0f) )); } if (block->m_name) SPEW((GROUP_STUFF_MEMORY, " %s", block->m_name)); SPEW((GROUP_STUFF_MEMORY, "")); block = block->m_next; } while (block != s_firstBlock); SPEW((GROUP_STUFF_MEMORY, "")); #endif } // //############################################################################# //############################################################################# // void MemoryBlockBase::CollapseBlocks() { #if defined(LAB_ONLY) SPEW((GROUP_STUFF_MEMORY, "MemoryBlock statistics prior to collapsing...")); UsageReport(); #endif MemoryBlockBase *block = s_firstBlock; do { Check_Object(block); block->Collapse(); block = block->m_next; } while (block != s_firstBlock); } #if 0 // //############################################################################# //############################################################################# // static int compare_function(const void* a, const void* b) { return static_cast(a) - static_cast(b); } #endif // //############################################################################# //############################################################################# // void MemoryBlockBase::Collapse() { #if !defined(MEMORY_VERIFY) // //--------------------------------------- // count up the number of deleted records //--------------------------------------- // BYTE *deletion = m_deletedRecord; size_t deletion_count = 0; while (deletion) { Check_Pointer(deletion); ++deletion_count; deletion = *Cast_Pointer(BYTE**, deletion); } if (!deletion_count) { return; } // //-------------------------------------------------------------------------- // Now, build a table of pointers that big and fill it in with the deletion // pointers. Fill it in backwards because the last blocks to be allocated // will probably be the last blocks to be deleted, and thus show up first in // the chain //-------------------------------------------------------------------------- // BYTE **deletions = new(m_heap) BYTE*[deletion_count+1]; deletion = m_deletedRecord; int i=deletion_count-1; deletions[deletion_count] = reinterpret_cast(SNAN_NEGATIVE_LONG); deletions[i--] = deletion; deletion = *Cast_Pointer(BYTE**, deletion); int j; while (deletion) { // //--------------------------------------------------- // We might as well build in the insertion stuff here //--------------------------------------------------- // Check_Pointer(deletion); j=i--; while (deletions[j+1] < deletion) { Verify(j < deletion_count); deletions[j] = deletions[j+1]; ++j; } deletions[j] = deletion; deletion = *Cast_Pointer(BYTE**, deletion); } Verify(i == -1); // //------------------------------------------------------------------------ // Now, step through each additional block and see if it can be eliminated //------------------------------------------------------------------------ // MemoryBlockHeader *last_real_block = m_blockMemory; MemoryBlockHeader *header = m_blockMemory->m_next; while (header) { // //-------------------------------------------------------------------- // try to locate the first data entry in the block in the sorted table //-------------------------------------------------------------------- // Check_Object(header); i = 0; j = deletion_count - 1; int m = 0; BYTE *key = Cast_Pointer(BYTE*, header) + sizeof(*header); while (i <= j) { m = (i + j) >> 1; if (deletions[m] == key) { break; } else if (deletions[m] > key) { j = m - 1; } else { i = m + 1; } } // //--------------------------------------------------------------------- // If the record wasn't found, then this block cannot be deleted, so it // is marked as the last real block and is so linked //--------------------------------------------------------------------- // if (i > j) { Real_Block: last_real_block->m_next = header; last_real_block = header; header = header->m_next; continue; } // //---------------------------------------------------------------------- // We found the beginning, so now we have to see if we can find the end. // If this block has a link, it was all used up. Otherwise, we have to // ignore the unused space at the end of the block //---------------------------------------------------------------------- // BYTE **start = &deletions[m]; if (header->m_next) { i = header->m_size / m_recordSize; } else { i = m_freeRecord - m_firstHeaderRecord; i /= m_recordSize; } // //--------------------------------------------------------------------- // Move the pointer to where the last entry for this block should be if // all used records where later deleted. If this is an impossible // location, or if the addresses do not match, this block cannot be // collapsed //--------------------------------------------------------------------- // BYTE **end = start + i - 1; if ( end - deletions >= deletion_count || *end != Cast_Pointer(BYTE*, header) + sizeof(*header) + (i-1)*m_recordSize ) { goto Real_Block; } // //------------------------------------------------------------------ // This block may now be killed, but save the next block address for // further checking //------------------------------------------------------------------ // MemoryBlockHeader *next_block = header->m_next; delete header; // //----------------------------------------------------------------------- // Now, move everything following our section of deleted records to where // our section begins, but only if there is something to move //----------------------------------------------------------------------- // ++end; j = Cast_Pointer(BYTE*, &deletions[deletion_count]) - Cast_Pointer(BYTE*, end); deletion_count -= i; if (j>0) { memmove(start, end, j); } // //---------------------------- // Check the next header block //---------------------------- // header = next_block; } // //-------------------------------------------------------------------- // Now, clean up the deletion array and reset all the current deletion // pointers //-------------------------------------------------------------------- // deletion = NULL; for (i=0; im_next = NULL; if ( m_firstHeaderRecord != Cast_Pointer(BYTE*, last_real_block) + sizeof(*last_real_block) ) { m_size = last_real_block->m_size; m_firstHeaderRecord = Cast_Pointer(BYTE*, last_real_block) + sizeof(*last_real_block); m_freeRecord = m_firstHeaderRecord + m_size; } #endif } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MemoryBlock ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // //############################################################################# // // This function allocates a fixed m_size record from the record blocks // //############################################################################# // void* MemoryBlock::New() { void *result; #if defined(MEMORY_VERIFY) result = new(m_heap) char[m_recordSize]; #else // //---------------------------------------------------------------------- // If we have a m_deletedRecord record, go ahead and reuse it, updating the next // m_deletedRecord record value from the m_firstHeaderRecord one. This chain is independant // of the m_firstHeaderRecord record block. If not, grow the m_blockMemory block by one // record //---------------------------------------------------------------------- // Check_Object(this); if (m_deletedRecord) { result = m_deletedRecord; m_deletedRecord = *Cast_Pointer(BYTE**, m_deletedRecord); } else { result = Grow(); } #endif // //---------------------------------------------------------------------- // If we are checking for unassigned variables, initialize the allocated // memory with NANs //---------------------------------------------------------------------- // #if defined(_ARMOR) Flood_Memory_With_NAN(result, m_recordSize); #endif // //------------------------------------- // Return the address of the new record //------------------------------------- // return result; } // //############################################################################# // // This function deallocates a record, making it available for re-use // //############################################################################# // void MemoryBlock::Delete( void* where ) { #if defined(MEMORY_VERIFY) delete where; #else // //-------------------------------------------------------------------- // If we are in debug2 mode, check to see if the m_deletedRecord region really // belongs to us //-------------------------------------------------------------------- // Check_Object(this); #if defined(MEMORY_BLOCK_VERIFY) // //-------------------------------------------------------------------- // Make sure the address of this record is not already in the deletion // chain //-------------------------------------------------------------------- // void *record; for ( record = m_deletedRecord; record; record = *Cast_Pointer(BYTE**, record) ) { if (record == where) { break; } } Verify(!record); // //------------------------------------------------------------------ // Find the address of the first record block, then check each block // until we come to the end of the chain //------------------------------------------------------------------ // unsigned offset; MemoryBlockHeader* block = m_blockMemory; while (block) { // //--------------------------------------------------------------- // If the record is in this block and is positioned correctly, go // ahead and break as we have found a legal place within a block //--------------------------------------------------------------- // Check_Object(block); offset = static_cast( Cast_Pointer(BYTE*, where) - Cast_Pointer(BYTE*, block + 1) ); if (offset < block->m_size) { Verify(!(offset % m_recordSize)); break; } // //--------------------- // Go to the next block //--------------------- // block = block->m_next; } Verify(block); // //----------------------------------------------------------- // Now make sure that the address is not in our future region //----------------------------------------------------------- // Verify(block->m_next || offset < m_freeRecord-m_firstHeaderRecord); #endif // //---------------------------------------------------------------------- // Make the first few bytes of the record act as the link pointer to the // beginning of the m_firstHeaderRecord m_deletedRecord chain, then make the first record // available for reuse this one //---------------------------------------------------------------------- // *Cast_Pointer(BYTE**, where) = m_deletedRecord; m_deletedRecord = Cast_Pointer(BYTE*, where); #endif } // //############################################################################# //############################################################################# // void* MemoryBlock::operator[](size_t index) { #if defined(MEMORY_VERIFY) STOP(("MemoryBlock::operator[] not available!\n")); #endif // //------------------------------------------------------------------ // Find the address of the first record block, then check each block // until we come to the end of the chain //------------------------------------------------------------------ // Check_Object(this); MemoryBlockHeader *block = m_blockMemory; while (block) { // //--------------------------------------------------------------- // If the index is in this block, go ahead and return its address //--------------------------------------------------------------- // Check_Object(block); Verify(m_recordSize); int records = block->m_size / m_recordSize; if (index < records) { return Cast_Pointer(BYTE*, block + 1) + index * m_recordSize; } // //-------------------------------------------------------------------- // Save the address of the next m_blockMemory block, then delete this one and // get ready to delete the next one //-------------------------------------------------------------------- // index -= records; block = block->m_next; } // //----------------------------------------- // The record doesn't exist, so return NULL //----------------------------------------- // return NULL; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MemoryStack ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // //############################################################################# //############################################################################# // void* MemoryStack::Push(const void* what) { Check_Object(this); Check_Pointer(what); BYTE *block = m_firstHeaderRecord; m_topOfStack = m_freeRecord; BYTE *target = static_cast(Grow()); Mem_Copy( target, what, m_recordSize, m_size - (target - m_firstHeaderRecord) ); if (m_firstHeaderRecord != block) { m_topOfStack = m_firstHeaderRecord; } return m_topOfStack; } // //############################################################################# //############################################################################# // void* MemoryStack::Push() { Check_Object(this); BYTE *block = m_firstHeaderRecord; m_topOfStack = m_freeRecord; Grow(); if (m_firstHeaderRecord != block) { m_topOfStack = m_firstHeaderRecord; } return m_topOfStack; } // //########################################################################### //########################################################################### // void MemoryStack::Pop() { MemoryBlockHeader *block, *new_block; // //----------------------------------------- // Make sure that something is in the stack //----------------------------------------- // Check_Object(this); if (m_topOfStack) { // //-------------------------------------------------------------------- // If the m_topOfStack of the stack is not at the bottom of a m_blockMemory block, the // m_freeRecord and m_topOfStack pointers can move normally //-------------------------------------------------------------------- // if (m_topOfStack != m_firstHeaderRecord) { // //------------------------------------------------------------------- // If the m_freeRecord pointer is at the bottom of a block, we have to delete // the block and update the variables to the previous block //------------------------------------------------------------------- // if (m_freeRecord == m_firstHeaderRecord) { new_block = Cast_Pointer(MemoryBlockHeader*, m_firstHeaderRecord - 1); Check_Object(new_block); for ( block = m_blockMemory; block->m_next != new_block; block = block->m_next ) { Check_Object(block); } delete new_block; block->m_next = NULL; m_firstHeaderRecord = Cast_Pointer(BYTE*, block + 1); m_size = block->m_size; } // //----------------------------------------------- // Move the m_topOfStack and m_freeRecord pointers back one record //----------------------------------------------- // m_freeRecord = m_topOfStack; m_topOfStack -= m_recordSize; } // //-------------------------------------------------------------------- // Otherwise, we have to wrap the top of the stack pointer back to the // previous block. If there is only one allocated block, then the // stack is empty //-------------------------------------------------------------------- // else if (!m_blockMemory->m_next) { m_topOfStack = NULL; } // //-------------------------------------------------------------------- // Point the top of the stack to the last record of the previous block //-------------------------------------------------------------------- // else { new_block = Cast_Pointer(MemoryBlockHeader*, m_firstHeaderRecord - 1); Check_Object(new_block); for ( block = m_blockMemory; block->m_next != new_block; block = block->m_next ) { Check_Object(block); } m_topOfStack = Cast_Pointer(BYTE*, block + 1) + block->m_size - m_recordSize; } } }