/* Copyright (C) 1994-2004 Revolution Software Ltd * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Header$ */ // memory manager // - "remember, it's not good to leave memory locked for a moment longer // than necessary" Tony // - "actually, in a sequential system theoretically you never need to lock // any memory!" Chris ;) // // This is a very simple implementation but I see little advantage to being // any cleverer with the coding - i could have put the mem blocks before the // defined blocks instead of in an array and then used pointers to // child/parent blocks. But why bother? I've Kept it simple. When it needs // updating or customising it will be accessable to anyone who looks at it. // // Doesn't have a purgeable/age consituant yet - if anyone wants this then // I'll add it in. // MemMan v1.1 #include "common/stdafx.h" #include "sword2/sword2.h" #include "sword2/resman.h" namespace Sword2 { #define MEMORY_POOL (1024 * 12000) // #define MEMDEBUG 1 MemoryManager::MemoryManager(Sword2Engine *vm) : _vm(vm) { uint32 j; uint8 *memory_base; _suggestedStart = 0; _totalFreeMemory = MEMORY_POOL; memory_base = (uint8 *) malloc(_totalFreeMemory); if (!memory_base) error("MemoryManager: couldn't malloc %d bytes", _totalFreeMemory); // the original malloc address _freeMemman = memory_base; // set all but first handle to unused for (j = 1; j < MAX_mem_blocks; j++) _memList[j].state = MEM_null; // total used (free, locked or floating) _totalBlocks = 1; _memList[0].ad = memory_base; _memList[0].state = MEM_free; _memList[0].age = 0; _memList[0].size = _totalFreeMemory; _memList[0].parent = -1; // we are base - for now _memList[0].child = -1; // we are the end as well _memList[0].uid = (uint32)UID_memman; // init id _baseMemBlock = 0; // for now } MemoryManager::~MemoryManager(void) { free(_freeMemman); } // I don't know about C++, but here's what "C: A Reference Manual" (Harbison & // Steele) has to say: // // "There is no requirement in C that any of the integral types be large enough // to represent a pointer, although C programmers often assume that type long // is large enough, which it is on most computers. In C99, header inttypes.h // may define integer types intptr_t and uintptr_t, which are guaranteed large // enough to hold a pointer as an integer." // // The script engine frequently needs to pass around pointers to various // structures etc. and, and used to do so by casting them to int32 and back // again. Since those pointers always point to memory that belongs to the // memory manager, we can easily represent them as offsets instead. int32 MemoryManager::ptrToInt(const uint8 *p) { debug(9, "ptrToInt: %p -> %d", p, p - _freeMemman); if (p < _freeMemman || p >= &_freeMemman[MEMORY_POOL]) warning("ptrToInt: Converting bad pointer: %p", p); return p - _freeMemman; } uint8 *MemoryManager::intToPtr(int32 n) { debug(9, "intToPtr: %d -> %p", n, &_freeMemman[n]); if (n < 0 || n >= MEMORY_POOL) warning("intToPtr: Converting bad integer: %d", n); return &_freeMemman[n]; } Memory *MemoryManager::lowLevelAlloc(uint32 size, uint32 type, uint32 unique_id) { // allocate a block of memory - locked or float // returns 0 if fails to allocate the memory // or a pointer to a mem structure int32 nu_block; uint32 spawn = 0; uint32 slack; // we must first round the size UP to a dword, so subsequent blocks // will start dword alligned size += 3; // move up size &= 0xfffffffc; // and back down to boundary // find a free block large enough // the defragger returns when its made a big enough block. This is // a good time to defrag as we're probably not doing anything super // time-critical at the moment if ((nu_block = defragMemory(size)) == -1) { // error - couldn't find a big enough space return 0; } // an exact fit? if (_memList[nu_block].size == size) { // no new block is required as the fit is perfect _memList[nu_block].state = type; // locked or float _memList[nu_block].size = size; // set to the required size _memList[nu_block].uid = unique_id; // an identifier #ifdef MEMDEBUG debugMemory(); #endif return &_memList[nu_block]; } // nu_block is the free block to split, forming our locked/float block // with a new free block in any remaining space // If our child is free then is can expand downwards to eat up our // chopped space this is good because it doesn't create an extra block // so keeping the block count down. // // Why? Imagine you Talloc 1000k, then free it. Now keep allocating 10 // bytes less and freeing again you end up with thousands of new free // mini blocks. This way avoids that as the free child keeps growing // downwards. if (_memList[nu_block].child != -1 && _memList[_memList[nu_block].child].state == MEM_free) { // our child is free // the spare memory is the blocks current size minus the // amount we're taking slack = _memList[nu_block].size - size; _memList[nu_block].state = type; // locked or float _memList[nu_block].size = size; // set to the required size _memList[nu_block].uid = unique_id; // an identifier // child starts after us _memList[_memList[nu_block].child].ad = _memList[nu_block].ad + size; // child's size increases _memList[_memList[nu_block].child].size += slack; return &_memList[nu_block]; } // otherwise we spawn a new block after us and before our child - our // child being a proper block that we cannot change // we remain a child of our parent // we spawn a new child and it inherits our current child // find a NULL slot for a new block while (_memList[spawn].state != MEM_null && spawn!=MAX_mem_blocks) spawn++; if (spawn == MAX_mem_blocks) { // run out of blocks - stop the program. this is a major blow // up and we need to alert the developer // Lets get a printout of this debugMemory(); error("Out of mem blocks in Talloc()"); } _memList[spawn].state = MEM_free; // new block is free _memList[spawn].uid = (uint32)UID_memman; // a memman created bloc // size of the existing parent free block minus the size of the new // space Talloc'ed. _memList[spawn].size = _memList[nu_block].size - size; // IOW the remaining memory is given to the new free block // we start 1 byte after the newly allocated block _memList[spawn].ad = _memList[nu_block].ad + size; // the spawned child gets it parent - the newly allocated block _memList[spawn].parent = nu_block; // the new child inherits the parents old child (we are its new // child "Waaaa") _memList[spawn].child = _memList[nu_block].child; // is the spawn the end block? if (_memList[spawn].child != -1) { // the child of the new free-spawn needs to know its new parent _memList[_memList[spawn].child].parent = spawn; } _memList[nu_block].state = type; // locked or float _memList[nu_block].size = size; // set to the required size _memList[nu_block].uid = unique_id; // an identifier // the new blocks new child is the newly formed free block _memList[nu_block].child = spawn; //we've brought a new block into the world. Ahhh! _totalBlocks++; #ifdef MEMDEBUG debugMemory(); #endif return &_memList[nu_block]; } void MemoryManager::freeMemory(Memory *block) { // kill a block of memory - which was presumably floating or locked // once you've done this the memory may be recycled block->state = MEM_free; block->uid = (uint32)UID_memman; // belongs to the memory manager again #ifdef MEMDEBUG debugMemory(); #endif } void MemoryManager::floatMemory(Memory *block) { // set a block to float // wont be trashed but will move around in memory block->state = MEM_float; #ifdef MEMDEBUG debugMemory(); #endif } void MemoryManager::lockMemory(Memory *block) { // set a block to lock // wont be moved - don't lock memory for any longer than necessary // unless you know the locked memory is at the bottom of the heap // can't move now - this block is now crying out to be floated or // free'd again block->state = MEM_locked; #ifdef MEMDEBUG debugMemory(); #endif } int32 MemoryManager::defragMemory(uint32 req_size) { // moves floating blocks down and/or merges free blocks until a large // enough space is found or there is nothing left to do and a big // enough block cannot be found we stop when we find/create a large // enough block - this is enough defragging. int32 cur_block; // block 0 remains the parent block int32 original_parent,child, end_child; uint32 j; uint32 *a; uint32 *b; // cur_block = _baseMemBlock; //the mother of all parents cur_block = _suggestedStart; do { // is current block a free block? if (_memList[cur_block].state == MEM_free) { if (_memList[cur_block].size >= req_size) { // this block is big enough - return its id return cur_block; } // the child is the end block - stop if the next block // along is the end block if (_memList[cur_block].child == -1) { // no luck, couldn't find a big enough block return -1; } // current free block is too small, but if its child // is *also* free then merge the two together if (_memList[_memList[cur_block].child].state == MEM_free) { // ok, we nuke the child and inherit its child child = _memList[cur_block].child; // our size grows by the size of our child _memList[cur_block].size += _memList[child].size; // our new child is our old childs, child _memList[cur_block].child = _memList[child].child; // not if the chld we're nuking is the end // child (it has no child) if (_memList[child].child != -1) { // the (nuked) old childs childs // parent is now us _memList[_memList[child].child].parent = cur_block; } // clean up the nuked child, so it can be used // again _memList[child].state = MEM_null; _totalBlocks--; } else if (_memList[_memList[cur_block].child].state == MEM_float) { // current free block is too small, but if its // child is a float then we move the floating // memory block down and the free up but, // parent/child relationships must be such // that the memory is all continuous between // blocks. ie. a childs memory always begins 1 // byte after its parent finishes. However, the // positions in the memory list may become // truly random, but, any particular block of // locked or floating memory must retain its // position within the _memList - the float // stays a float because the handle/pointer // has been passed back // // what this means is that when the physical // memory of the foat moves down (and the free // up) the child becomes the parent and the // parent the child but, remember, the parent // had a parent and the child another child - // these swap over too as the parent/child swap // takes place - phew. // our child is currently floating child = _memList[cur_block].child; // move the higher float down over the free // block // memcpy(_memList[cur_block].ad, _memList[child].ad, _memList[child].size); a = (uint32 *) _memList[cur_block].ad; b = (uint32 *) _memList[child].ad; for (j = 0; j < _memList[child].size / 4; j++) *(a++) = *(b++); // both *ad's change // the float is now where the free was and the // free goes up by the size of the float // (which has come down) _memList[child].ad = _memList[cur_block].ad; _memList[cur_block].ad += _memList[child].size; // the status of the _memList blocks must // remain the same, so... // our child gets this when we become its // child and it our parent original_parent = _memList[cur_block].parent; // the free's child becomes its parent _memList[cur_block].parent = child; // the new child inherits its previous childs // child _memList[cur_block].child = _memList[child].child; // save this - see next line end_child = _memList[child].child; // the floats parent becomes its child _memList[child].child = cur_block; _memList[child].parent = original_parent; // if the child had a child if (end_child != -1) { // then its parent is now the new child _memList[end_child].parent = cur_block; } // if the base block was the true base parent if (original_parent == -1) { // then the child that has moved down // becomes the base block as it sits // at the lowest possible memory // location _baseMemBlock = child; } else { // otherwise the parent of the current // free block - that is now the child // - gets a new child, that child // being previously the child of the // child of the original parent _memList[original_parent].child = child; } } else { // if (_memList[_memList[cur_block].child].state == MEM_lock) // the child of current is locked - move to it // move to next one along - either locked or // END cur_block=_memList[cur_block].child; } } else { // move to next one along, the current must be // floating, locked, or a NULL slot cur_block = _memList[cur_block].child; } } while (cur_block != -1); // while the block we've just done is not the final block return -1; //no luck, couldn't find a big enough block } void MemoryManager::debugMemory(void) { // gets called with lowLevelAlloc, Mem_free, Mem_lock & Mem_float if // MEMDEBUG has been #defined otherwise can be called at any time // anywhere else int j; char inf[][20] = { { "MEM_null" }, { "MEM_free" }, { "MEM_locked" }, { "MEM_float" } }; debug(5, "base %d total %d", _baseMemBlock, _totalBlocks); // first in mem list order for (j = 0; j < MAX_mem_blocks; j++) { if (_memList[j].state == MEM_null) debug(5, "%d- NULL", j); else debug(5, "%d- state %s, ad %d, size %d, p %d, c %d, id %d", j, inf[_memList[j].state], _memList[j].ad, _memList[j].size, _memList[j].parent, _memList[j].child, _memList[j].uid); } // now in child/parent order j = _baseMemBlock; do { debug(5, " %d- state %s, ad %d, size %d, p %d, c %d", j, inf[_memList[j].state], _memList[j].ad, _memList[j].size, _memList[j].parent, _memList[j].child, _memList[j].uid); j = _memList[j].child; } while (j != -1); } Memory *MemoryManager::allocMemory(uint32 size, uint32 type, uint32 unique_id) { // the high level allocator // can ask the resman to remove old resources to make space - will // either do it or halt the system Memory *membloc; int j; uint32 free = 0; while (virtualDefrag(size)) { // trash the oldest closed resource if (!_vm->_resman->helpTheAgedOut()) { error("alloc ran out of memory: size=%d type=%d unique_id=%d", size, type, unique_id); } } membloc = lowLevelAlloc(size, type, unique_id); if (membloc == 0) { error("lowLevelAlloc failed to get memory virtualDefrag said was there"); } j = _baseMemBlock; do { if (_memList[j].state == MEM_free) free += _memList[j].size; j = _memList[j].child; } while (j != -1); // return the pointer to the memory return membloc; } // Maximum allowed wasted memory. #define MAX_WASTAGE 51200 int32 MemoryManager::virtualDefrag(uint32 size) { // Virutually defrags memory... // // Used to determine if there is potentially are large enough free // block available is the real defragger was allowed to run. // // The idea being that alloc will call this and help_the_aged_out // until we indicate that it is possible to obtain a large enough // free block. This way the defragger need only run once to yield the // required block size. // // The reason for its current slowness is that the defragger is // potentially called several times, each time shifting upto 20Megs // around, to obtain the required free block. int32 cur_block; uint32 currentBubbleSize = 0; cur_block = _baseMemBlock; _suggestedStart = _baseMemBlock; do { if (_memList[cur_block].state == MEM_free) { // Add a little intelligence. At the start the oldest // resources are at the bottom of the tube. However // there will be some air at the top. Thus bubbles // will be created at the bottom and float to the // top. If we ignore the top gap then a large enough // bubble will form lower down the tube. Thus less // memory will need to be shifted. if (_memList[cur_block].child != -1) currentBubbleSize += _memList[cur_block].size; else if (_memList[cur_block].size > MAX_WASTAGE) currentBubbleSize += _memList[cur_block].size; if (currentBubbleSize >= size) return 0; } else if (_memList[cur_block].state == MEM_locked) { currentBubbleSize = 0; // Any free block of the correct size will be above // this locked block. _suggestedStart = _memList[cur_block].child; } cur_block = _memList[cur_block].child; } while (cur_block != -1); return 1; } } // End of namespace Sword2