| /********************************************************************** |
| * File: memblk.c (Formerly memblock.c) |
| * Description: Enhanced instrumented memory allocator implemented as a class. |
| * Author: Ray Smith |
| * Created: Tue Jan 21 17:13:39 GMT 1992 |
| * |
| * (C) Copyright 1992, Hewlett-Packard Ltd. |
| ** Licensed under the Apache License, Version 2.0 (the "License"); |
| ** you may not use this file except in compliance with the License. |
| ** You may obtain a copy of the License at |
| ** http://www.apache.org/licenses/LICENSE-2.0 |
| ** Unless required by applicable law or agreed to in writing, software |
| ** distributed under the License is distributed on an "AS IS" BASIS, |
| ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| ** See the License for the specific language governing permissions and |
| ** limitations under the License. |
| * |
| **********************************************************************/ |
| |
| #include "mfcpch.h" //precompiled headers |
| #include <stdlib.h> |
| #include <string.h> |
| #include "stderr.h" |
| #include "memryerr.h" |
| #include "hashfn.h" |
| #include "tprintf.h" |
| #include "memry.h" |
| #include "memblk.h" |
| #ifdef __UNIX__ |
| #include <signal.h> |
| #endif |
| |
| class UWREC |
| { |
| public: |
| unsigned cur_frsize; //frame size |
| unsigned cursp; //stack |
| unsigned currls; //pc space |
| unsigned currlo; //pc offset |
| unsigned curdp; //data pointer |
| unsigned toprp; //rp |
| unsigned topmrp; //mrp |
| unsigned topsr0; //sr0 |
| unsigned topsr4; //sr4 |
| unsigned r3; //gr3 |
| unsigned cur_r19; //gr19 |
| }; |
| |
| MEMUNION *free_block = NULL; //head of freelist |
| |
| #define EXTERN |
| |
| EXTERN MEM_ALLOCATOR big_mem; |
| EXTERN MEM_ALLOCATOR main_mem; |
| //heads of freelists |
| EXTERN MEMUNION *free_structs[MAX_STRUCTS]; |
| //number issued |
| EXTERN inT32 structs_in_use[MAX_STRUCTS]; |
| //number issued |
| EXTERN inT32 blocks_in_use[MAX_STRUCTS]; |
| //head of block lists |
| EXTERN MEMUNION *struct_blocks[MAX_STRUCTS]; |
| EXTERN const char *owner_names[MAX_STRUCTS][MAX_CLASSES]; |
| EXTERN inT32 owner_counts[MAX_STRUCTS][MAX_CLASSES]; |
| //no of names |
| EXTERN inT16 name_counts[MAX_STRUCTS]; |
| EXTERN inT32 free_struct_blocks; //no of free blocks |
| |
| EXTERN INT_VAR (mem_mallocdepth, 0, "Malloc stack depth to trace"); |
| EXTERN INT_VAR (mem_mallocbits, 8, "Log 2 of hash table size"); |
| EXTERN INT_VAR (mem_freedepth, 0, "Free stack dpeth to trace"); |
| EXTERN INT_VAR (mem_freebits, 8, "Log 2 of hash table size"); |
| EXTERN INT_VAR (mem_countbuckets, 16, "No of buckets for histogram"); |
| EXTERN INT_VAR (mem_checkfreq, 0, "Calls to alloc_mem between owner counts"); |
| |
| /********************************************************************** |
| * MEM_ALLOCATOR::MEM_ALLOCATOR |
| * |
| * Constructor for a memory allocator. |
| **********************************************************************/ |
| |
| void |
| MEM_ALLOCATOR::init ( //initialize |
| void *(*ext_malloc) (inT32), //external source |
| void (*ext_free) (void *), //external free |
| inT32 firstsize, //size of first block |
| inT32 lastsize, //size of last block |
| inT32 maxchunk //biggest request |
| ) { |
| blockcount = 0; |
| malloc_serial = 0; |
| topblock = NULL; |
| currblock = NULL; |
| callers = NULL; |
| malloc = ext_malloc; |
| free = ext_free; |
| maxsize = lastsize; |
| biggestblock = maxchunk; |
| totalmem = 0; |
| memsize = firstsize; |
| malloc_div_ratio = 1; |
| malloc_minor_serial = 0; |
| malloc_auto_count = 0; |
| call_bits = 0; |
| entries = 0; |
| } |
| |
| |
| /********************************************************************** |
| * MEM_ALLOCATOR::hash_caller |
| * |
| * Generate a hash code for a caller, setup the tables if necessary. |
| **********************************************************************/ |
| |
| uinT16 MEM_ALLOCATOR::hash_caller( //get hash code |
| void *addr //return address |
| ) { |
| inT32 index; //index to table |
| inT32 initial_hash; //initial index |
| |
| if (callers == NULL) |
| init_callers(); //setup table |
| //get hash code |
| initial_hash = hash (call_bits, &addr, sizeof (addr)); |
| if (initial_hash == 0) |
| initial_hash = 1; |
| index = initial_hash; |
| if (callers[index].caller != NULL && callers[index].caller != addr) { |
| do { |
| index++; |
| if (index >= entries) |
| index = 1; |
| } |
| while (callers[index].caller != NULL |
| && callers[index].caller != addr && index != initial_hash); |
| if (index == initial_hash) |
| index = 0; |
| } |
| if (callers[index].caller == NULL) { |
| if (index != 0) |
| callers[index].caller = addr; |
| if (callers[index].free_list == NULL) |
| //setup free table |
| callers[index].init_freeers (); |
| } |
| return (uinT16) index; |
| } |
| |
| |
| /********************************************************************** |
| * MALLOC_CALL::count_freeer |
| * |
| * Generate a hash code for a freeer, setup the tables if necessary. |
| * Then count the call. |
| **********************************************************************/ |
| |
| void MALLOC_CALL::count_freeer( //count calls to free |
| void *addr //return address |
| ) { |
| inT32 entries; //entries in table |
| inT32 index; //index to table |
| inT32 initial_hash; //initial index |
| |
| if (free_list == NULL) |
| init_freeers(); //setup table |
| entries = 1 << free_bits; |
| //get hash code |
| initial_hash = hash (free_bits, &addr, sizeof (addr)); |
| if (initial_hash == 0) |
| initial_hash = 1; |
| index = initial_hash; |
| if (free_list[index].freeer != NULL && free_list[index].freeer != addr) { |
| do { |
| index++; |
| if (index >= entries) |
| index = 1; |
| } |
| while (free_list[index].freeer != NULL |
| && free_list[index].freeer != addr && index != initial_hash); |
| if (index == initial_hash) |
| index = 0; |
| } |
| if (free_list[index].freeer == NULL && index != 0) { |
| free_list[index].freeer = addr; |
| } |
| free_list[index].count++; //count them |
| } |
| |
| |
| /********************************************************************** |
| * MEM_ALLOCATOR::init_callers |
| * |
| * Initialize the callers hash table. |
| **********************************************************************/ |
| |
| void MEM_ALLOCATOR::init_callers() { //setup hash table |
| inT32 depth = mem_mallocdepth; |
| |
| mem_mallocdepth.set_value (0); //can't register it |
| call_bits = mem_mallocbits; |
| entries = 1 << call_bits; |
| //make an array |
| callers = new MALLOC_CALL[entries]; |
| mem_mallocdepth.set_value (depth); |
| } |
| |
| |
| /********************************************************************** |
| * MALLOC_CALL::init_freeers |
| * |
| * Initialize the freeers hash table. |
| **********************************************************************/ |
| |
| void MALLOC_CALL::init_freeers() { //setup hash table |
| inT32 entries; //entries in table |
| inT32 depth = mem_mallocdepth; |
| |
| mem_mallocdepth.set_value (0); //can't register it |
| free_bits = mem_freebits; |
| entries = 1 << free_bits; |
| //make an array |
| free_list = new FREE_CALL[entries]; |
| mem_mallocdepth.set_value (depth); |
| } |
| |
| |
| /********************************************************************** |
| * MEM_ALLOCATOR::reduce_counts |
| * |
| * Divide all ages by 2 to get a log use of counts. |
| **********************************************************************/ |
| |
| void MEM_ALLOCATOR::reduce_counts() { //divide by 2 |
| MEMBLOCK *block; //current block |
| MEMUNION *chunk; //current chunk |
| inT32 chunksize; //size of chunk |
| inT32 blockindex; //index of block |
| |
| check_mem ("Reducing counts", JUSTCHECKS); |
| for (blockindex = 0; blockindex < blockcount; blockindex++) { |
| //current block |
| block = &memblocks[blockindex]; |
| //scan all chunks |
| for (chunk = block->blockstart; chunk != block->blockend; chunk += chunksize) { |
| chunksize = chunk->size; //size of chunk |
| if (chunksize < 0) |
| chunksize = -chunksize; //absolute size |
| chunk->age /= 2; //divide ages |
| } |
| } |
| } |
| |
| |
| /********************************************************************** |
| * MEM_ALLOCATOR::display_counts |
| * |
| * Send counts of outstanding blocks to stderr. |
| **********************************************************************/ |
| |
| void MEM_ALLOCATOR::display_counts() { //count up |
| MEMBLOCK *block; //current block |
| MEMUNION *chunk; //current chunk |
| inT32 chunksize; //size of chunk |
| inT32 blockindex; //index of block |
| inT32 buckets; //required buckets |
| inT32 bucketsize; //no in each bucket |
| inT32 callindex; //index to callers |
| inT32 freeindex; //index to freeers |
| inT32 freeentries; //table size |
| inT32 totalchunks; //total chunk counts |
| inT32 totalspace; //total mem space |
| inT32 totalpchunks; //permanent chunks |
| inT32 totalpspace; //permanent space |
| inT32 totalfrees; //total free calls |
| |
| if (callers == NULL) |
| return; //can't do anything |
| check_mem ("Displaying counts", JUSTCHECKS); |
| buckets = mem_countbuckets; |
| bucketsize = (malloc_serial - 1) / buckets + 1; |
| tprintf ("\nEach bucket covers %g counts.\n", |
| (double) bucketsize * malloc_div_ratio); |
| for (callindex = 0; callindex < entries; callindex++) { |
| if (callers[callindex].free_list != NULL) { |
| callers[callindex].counts = |
| (inT32 *) malloc (buckets * 4 * sizeof (inT32)); |
| memset (callers[callindex].counts, 0, |
| (size_t) (buckets * 4 * sizeof (inT32))); |
| } |
| } |
| for (blockindex = 0; blockindex < blockcount; blockindex++) { |
| //current block |
| block = &memblocks[blockindex]; |
| //scan all chunks |
| for (chunk = block->blockstart; chunk != block->topchunk; chunk += chunksize) { |
| chunksize = chunk->size; //size of chunk |
| if (chunksize < 0) { |
| chunksize = -chunksize; //absolute size |
| callindex = chunk->owner; |
| if (callers[callindex].counts != NULL) { |
| callers[callindex].counts[chunk->age / bucketsize * 4]++; |
| callers[callindex].counts[chunk->age / bucketsize * 4 + |
| 1] += chunksize; |
| } |
| } |
| } |
| //scan all chunks |
| for (; chunk != block->blockend; chunk += chunksize) { |
| chunksize = chunk->size; //size of chunk |
| if (chunksize < 0) { |
| chunksize = -chunksize; //absolute size |
| callindex = chunk->owner; |
| if (callers[callindex].counts != NULL) { |
| callers[callindex].counts[chunk->age / bucketsize * 4 + |
| 2]++; |
| callers[callindex].counts[chunk->age / bucketsize * 4 + |
| 3] += chunksize; |
| } |
| } |
| } |
| } |
| for (callindex = 0; callindex < entries; callindex++) { |
| if (callers[callindex].counts != NULL) { |
| for (totalspace = 0, totalchunks = 0, totalpspace = |
| 0, totalpchunks = 0, freeindex = 0; freeindex < buckets; |
| freeindex++) { |
| totalchunks += callers[callindex].counts[freeindex * 4]; |
| totalspace += callers[callindex].counts[freeindex * 4 + 1]; |
| totalpchunks += callers[callindex].counts[freeindex * 4 + 2]; |
| totalpspace += callers[callindex].counts[freeindex * 4 + 3]; |
| } |
| freeentries = 1 << callers[callindex].free_bits; |
| for (totalfrees = 0, freeindex = 0; freeindex < freeentries; |
| freeindex++) |
| totalfrees += callers[callindex].free_list[freeindex].count; |
| if (totalspace != 0 || totalfrees != 0) { |
| tprintf ("alloc_mem at %d : total held=%d(%d), frees=%d.\n", |
| callers[callindex].caller, |
| totalchunks, totalspace * sizeof (MEMUNION), |
| totalfrees); |
| } |
| if (totalspace > 0) { |
| for (freeindex = 0; freeindex < buckets; freeindex++) { |
| tprintf ("%d(%d) ", |
| callers[callindex].counts[freeindex * 4], |
| callers[callindex].counts[freeindex * 4 + |
| 1] * sizeof (MEMUNION)); |
| } |
| tprintf ("\n"); |
| } |
| if (totalfrees != 0) { |
| tprintf ("Calls to free : "); |
| for (freeindex = 0; freeindex < freeentries; freeindex++) { |
| if (callers[callindex].free_list[freeindex].count != 0) |
| tprintf ("%d : %d ", |
| callers[callindex].free_list[freeindex].freeer, |
| callers[callindex].free_list[freeindex].count); |
| } |
| tprintf ("\n"); |
| } |
| if (totalpspace != 0) { |
| tprintf ("alloc_mem_p at %d : total held=%d(%d).\n", |
| callers[callindex].caller, |
| totalpchunks, totalpspace * sizeof (MEMUNION)); |
| for (freeindex = 0; freeindex < buckets; freeindex++) { |
| tprintf ("%d(%d) ", |
| callers[callindex].counts[freeindex * 4 + 2], |
| callers[callindex].counts[freeindex * 4 + |
| 3] * sizeof (MEMUNION)); |
| } |
| tprintf ("\n"); |
| } |
| free (callers[callindex].counts); |
| callers[callindex].counts = NULL; |
| } |
| } |
| } |
| |
| |
| /********************************************************************** |
| * MEM_ALLOCATOR::check |
| * |
| * Check consistency of all memory controlled by this allocator. |
| **********************************************************************/ |
| |
| void MEM_ALLOCATOR::check( //check consistency |
| const char *string, //context message |
| inT8 level //level of check |
| ) { |
| MEMBLOCK *block; //current block |
| MEMUNION *chunk; //current chunk |
| MEMUNION *prevchunk; //previous chunk |
| inT32 chunksize; //size of chunk |
| inT32 usedcount; //no of used chunks |
| inT32 usedsize; //size of used chunks |
| inT32 freecount; //no of free chunks |
| inT32 freesize; //size of free chunks |
| inT32 biggest; //biggest free chunk |
| inT32 totusedcount; //no of used chunks |
| inT32 totusedsize; //size of used chunks |
| inT32 totfreecount; //no of free chunks |
| inT32 totfreesize; //size of free chunks |
| inT32 totbiggest; //biggest free chunk |
| inT32 totblocksize; //total size of blocks |
| inT32 chunkindex; //index of chunk |
| inT32 blockindex; //index of block |
| |
| if (level >= MEMCHECKS) |
| tprintf ("\nMEM_ALLOCATOR::check:at '%s'\n", string); |
| totusedcount = 0; //grand totals |
| totusedsize = 0; |
| totfreecount = 0; |
| totfreesize = 0; |
| totbiggest = 0; |
| totblocksize = 0; |
| for (blockindex = 0; blockindex < blockcount; blockindex++) { |
| //current block |
| block = &memblocks[blockindex]; |
| if (level >= MEMCHECKS) |
| tprintf ("Block %d:0x%x-0x%x, size=%d, top=0x%x, l=%d, u=%d\n", |
| blockindex, block->blockstart, block->blockend, |
| (block->blockend - block->blockstart) * sizeof (MEMUNION), |
| block->topchunk, block->lowerspace, block->upperspace); |
| usedcount = usedsize = 0; //zero counters |
| freecount = freesize = 0; //zero counters |
| biggest = 0; |
| //scan all chunks |
| for (chunkindex = 0, prevchunk = NULL, chunk = block->blockstart; chunk != block->blockend; chunkindex++, chunk += chunksize) { |
| chunksize = chunk->size; //size of chunk |
| if (chunksize < 0) |
| chunksize = -chunksize; //absolute size |
| if (level >= FULLMEMCHECKS) { |
| tprintf ("%5d=%8d%c caller=%d, age=%d ", (int) chunkindex, |
| chunksize * sizeof (MEMUNION), |
| chunk->size < 0 ? 'U' : 'F', chunk->owner, chunk->age); |
| if (chunkindex % 5 == 4) |
| tprintf ("\n"); |
| } |
| //illegal sizes |
| if (chunksize == 0 || chunk->size == -1 |
| //out of bounds |
| || chunk + chunksize - block->blockstart <= 0 || block->blockend - (chunk + chunksize) < 0) |
| BADMEMCHUNKS.error ("check_mem", ABORT, |
| "Block=%p, Prev chunk=%p, Chunk=%p, Size=%x", |
| block, prevchunk, chunk, |
| (int) chunk->size); |
| |
| else if (chunk->size < 0) { |
| usedcount++; //used block |
| usedsize += chunksize; |
| } |
| else { |
| freecount++; //free block |
| freesize += chunksize; |
| if (chunksize > biggest) |
| biggest = chunksize; |
| } |
| prevchunk = chunk; |
| } |
| if (level >= MEMCHECKS) { |
| if (level >= FULLMEMCHECKS) |
| tprintf ("\n"); |
| tprintf ("%d chunks in use, total size=%d bytes\n", |
| (int) usedcount, usedsize * sizeof (MEMUNION)); |
| tprintf ("%d chunks free, total size=%d bytes\n", |
| (int) freecount, freesize * sizeof (MEMUNION)); |
| tprintf ("Largest free fragment=%d bytes\n", |
| biggest * sizeof (MEMUNION)); |
| } |
| totusedcount += usedcount; //grand totals |
| totusedsize += usedsize; |
| totfreecount += freecount; |
| totfreesize += freesize; |
| if (biggest > totbiggest) |
| totbiggest = biggest; |
| totblocksize += block->blockend - block->blockstart; |
| } |
| if (level >= MEMCHECKS) { |
| tprintf ("%d total blocks in use, total size=%d bytes\n", |
| blockcount, totblocksize * sizeof (MEMUNION)); |
| tprintf ("%d total chunks in use, total size=%d bytes\n", |
| (int) totusedcount, totusedsize * sizeof (MEMUNION)); |
| tprintf ("%d total chunks free, total size=%d bytes\n", |
| (int) totfreecount, totfreesize * sizeof (MEMUNION)); |
| tprintf ("Largest free fragment=%d bytes\n", |
| totbiggest * sizeof (MEMUNION)); |
| } |
| if (level >= MEMCHECKS) |
| display_counts(); |
| } |
| |
| |
| /********************************************************************** |
| * MEM_ALLOCATOR::alloc_p |
| * |
| * Allocate permanent space which will never be returned. |
| * This space is allocated from the top end of a memory block to |
| * avoid the fragmentation which would result from alternate use |
| * of alloc_mem for permanent and temporary blocks. |
| **********************************************************************/ |
| |
| void *MEM_ALLOCATOR::alloc_p( //permanent space |
| inT32 count, //block size to allocate |
| void *caller //ptr to caller |
| ) { |
| MEMBLOCK *block; //current block |
| MEMUNION *chunk; //current chunk |
| |
| if (count < 1 || count > biggestblock) |
| //request too big |
| MEMTOOBIG.error ("alloc_mem_p", ABORT, "%d", (int) count); |
| |
| count += sizeof (MEMUNION) - 1;//round up to word |
| count /= sizeof (MEMUNION); |
| count++; //and add one |
| if (topblock == NULL) { |
| topblock = new_block (count);//get first block |
| currblock = topblock; |
| if (topblock == NULL) { |
| check_mem ("alloc_mem_p returning NULL", MEMCHECKS); |
| return NULL; |
| } |
| } |
| block = topblock; //current block |
| do { |
| chunk = block->topchunk; |
| if (chunk->size < count) |
| block = block->next; //try next block |
| } |
| //until all tried |
| while (chunk->size < count && block != topblock); |
| if (chunk->size < count) { //still no good |
| chunk = (MEMUNION *) alloc ((count - 1) * sizeof (MEMUNION), caller); |
| //try last resort |
| if (chunk != NULL) |
| return chunk; |
| check_mem ("alloc_mem_p returning NULL", MEMCHECKS); |
| return NULL; |
| } |
| block->upperspace -= count; //less above freechunk |
| if (chunk->size > count) { |
| chunk->size -= count; |
| chunk += chunk->size; |
| } |
| chunk->size = -count; //mark as in use |
| if (mem_mallocdepth > 0) { |
| set_owner(chunk, caller); |
| } |
| else { |
| chunk->owner = 0; |
| chunk->age = 0; |
| } |
| return chunk + 1; //created chunk |
| } |
| |
| |
| /********************************************************************** |
| * MEM_ALLOCATOR::alloc |
| * |
| * Return a pointer to a buffer of count bytes aligned for any type. |
| **********************************************************************/ |
| |
| void *MEM_ALLOCATOR::alloc( //get memory |
| inT32 count, //no of bytes to get |
| void *caller //ptr to caller |
| ) { |
| MEMBLOCK *block; //current block |
| MEMUNION *chunk; //current chunk |
| inT32 chunksize; //size of free chunk |
| MEMUNION *chunkstart; //start of free chunk |
| |
| if (count < 1 || count > biggestblock) |
| MEMTOOBIG.error ("alloc_mem", ABORT, "%d", (int) count); |
| //request too big |
| |
| count += sizeof (MEMUNION) - 1;//round up to word |
| count /= sizeof (MEMUNION); |
| count++; //and add one |
| if (currblock == NULL) { |
| //get first block |
| currblock = new_block (count); |
| topblock = currblock; |
| if (currblock == NULL) { |
| check_mem ("alloc_mem returning NULL", MEMCHECKS); |
| return NULL; |
| } |
| } |
| block = currblock; //current block |
| if (block->upperspace <= block->lowerspace) { |
| //restart chunklist |
| block->freechunk = block->blockstart; |
| block->upperspace += block->lowerspace; |
| block->lowerspace = 0; //correct space counts |
| } |
| chunk = block->freechunk; //current free chunk |
| if (chunk->size < count) { //big enough? |
| do { |
| //search for free chunk |
| chunk = block->find_chunk (count); |
| if (chunk->size < count) |
| block = block->next; //try next block |
| } |
| //until all tried |
| while (chunk->size < count && block != currblock); |
| if (chunk->size < count) { //still no good |
| //get a new block |
| currblock = new_block (count); |
| topblock = currblock; //set perms here too |
| if (currblock == NULL) { |
| check_mem ("alloc_mem returning NULL", MEMCHECKS); |
| return NULL; |
| } |
| block = currblock; |
| chunk = block->freechunk; //bound to be big enough |
| } |
| } |
| chunkstart = chunk; //start of chunk |
| if (chunk == block->topchunk && chunk + count != block->blockend) |
| block->topchunk += count; //top has moved |
| block->upperspace -= count; //upper part used |
| chunksize = chunk->size; //size of free chunk |
| chunk->size = -count; //mark as used |
| chunk += count; //next free space |
| totalmem -= count; //no of free elements |
| if (chunksize > count) //bigger than exact? |
| //remaining space |
| chunk->size = chunksize - count; |
| else if (chunk == block->blockend) { |
| chunk = block->blockstart; //restart block |
| block->upperspace = block->lowerspace; |
| block->lowerspace = 0; //fix space counts |
| } |
| block->freechunk = chunk; //next free block |
| if (mem_mallocdepth > 0) { |
| set_owner(chunkstart, caller); |
| } |
| else { |
| chunkstart->owner = 0; |
| chunkstart->age = 0; |
| } |
| chunkstart++; //start of block |
| return chunkstart; //pointer to block |
| } |
| |
| |
| /********************************************************************** |
| * MEM_ALLOCATOR::set_owner |
| * |
| * Set the owner and time stamp of the block and check if needed. |
| **********************************************************************/ |
| |
| void MEM_ALLOCATOR::set_owner( //get memory |
| MEMUNION *chunkstart, //chunk to set |
| void *caller //ptr to caller |
| ) { |
| uinT16 callindex; //hash code |
| |
| callindex = hash_caller (caller); |
| chunkstart->owner = callindex; |
| //store evidence |
| chunkstart->age = malloc_serial; |
| malloc_minor_serial++; |
| if (malloc_minor_serial >= malloc_div_ratio) { |
| malloc_minor_serial = 0; |
| malloc_serial++; //count calls |
| if (malloc_serial == 0) { |
| //wrap around |
| reduce_counts(); //fix serial numbers |
| malloc_serial = MAX_INT16 + 1; |
| //all worth double |
| malloc_div_ratio += malloc_div_ratio; |
| } |
| } |
| malloc_auto_count++; |
| if (mem_checkfreq > 0 && malloc_auto_count >= (uinT32) mem_checkfreq) { |
| malloc_auto_count = 0; |
| check_mem ("Auto check", MEMCHECKS); |
| } |
| } |
| |
| |
| /********************************************************************** |
| * MEM_ALLOCATOR::dealloc |
| * |
| * Free a block allocated by alloc (or alloc_p). |
| * It checks that the pointer is legal and maintains counts of the |
| * amount of free memory above and below the current free pointer. |
| **********************************************************************/ |
| |
| void MEM_ALLOCATOR::dealloc( //free memory |
| void *oldchunk, //chunk to free |
| void *caller //ptr to caller |
| ) { |
| MEMUNION *chunk; //current chunk |
| MEMBLOCK *block; //current block |
| |
| if (oldchunk == NULL) |
| FREENULLPTR.error ("free_mem", ABORT, NULL); |
| chunk = (MEMUNION *) oldchunk; |
| block = currblock; //current block |
| if (block == NULL) |
| NOTMALLOCMEM.error ("free_mem", ABORT, NULL); |
| do { |
| block = block->next; |
| } |
| //outside the block |
| while ((chunk - block->blockstart < 0 || block->blockend - chunk <= 0) |
| && block != currblock); |
| |
| if (chunk - block->blockstart < 0 || block->blockend - chunk <= 0) |
| //in no block |
| NOTMALLOCMEM.error ("free_mem", ABORT, NULL); |
| |
| chunk--; //point to size |
| if (chunk->size == 0) |
| //zero size |
| FREEILLEGALPTR.error ("free_mem", ABORT, NULL); |
| else if (chunk->size > 0) |
| //already free |
| FREEFREEDBLOCK.error ("free_mem", ABORT, NULL); |
| chunk->size = -chunk->size; //mark it free |
| if (mem_freedepth > 0 && callers != NULL) { |
| //count calls |
| callers[chunk->owner].count_freeer (caller); |
| } |
| totalmem += chunk->size; //total free memory |
| if (chunk - block->freechunk < 0) |
| //extra below |
| block->lowerspace += chunk->size; |
| else |
| //extra above |
| block->upperspace += chunk->size; |
| } |
| |
| |
| /********************************************************************** |
| * MEM_ALLOCATOR::new_block |
| * |
| * Gets a new big block of memory from malloc for use by alloc_mem. |
| **********************************************************************/ |
| |
| MEMBLOCK *MEM_ALLOCATOR::new_block( //get new big block |
| inT32 minsize //minimum size |
| ) { |
| MEMBLOCK *newblock; //new block |
| |
| if (blockcount >= MAXBLOCKS) { |
| //can't have another |
| NOMOREBLOCKS.error ("mem_new_block", TESSLOG, NULL); |
| return NULL; |
| } |
| if (mem_checkfreq != 0) { |
| tprintf ("\nGetting new block due to request size of %d", |
| minsize * sizeof (MEMUNION)); |
| tprintf (" from %d from %d from %d from %d from %d\n", |
| trace_caller (3), trace_caller (4), trace_caller (5), |
| trace_caller (6), trace_caller (7)); |
| check_mem ("Getting new block", MEMCHECKS); |
| } |
| //get a new one |
| newblock = &memblocks[blockcount++]; |
| while (memsize < minsize) |
| memsize *= 4; //go up in sizes |
| //get a big block |
| newblock->blockstart = (MEMUNION *) |
| malloc (memsize * sizeof (MEMUNION)); |
| if (newblock->blockstart == NULL) { |
| NOMOREMEM.error ("mem_new_block", TESSLOG, NULL); |
| |
| #ifdef __UNIX__ |
| raise(SIGTTOU); //hangup for js |
| #endif |
| return NULL; |
| } |
| //end of block |
| newblock->blockend = newblock->blockstart + memsize; |
| //first free chunk |
| newblock->freechunk = newblock->blockstart; |
| newblock->topchunk = newblock->blockstart; |
| newblock->lowerspace = 0; |
| newblock->upperspace = memsize;//amount available |
| //set pointer |
| newblock->freechunk->size = memsize; |
| newblock->freechunk->owner = 0; |
| newblock->freechunk->age = 0; |
| |
| totalmem += memsize; //total assigned mem |
| |
| if (memsize < maxsize) |
| memsize *= 4; //successively bigger |
| if (currblock == NULL) { |
| newblock->next = newblock; //first block |
| } |
| else { |
| //insert in list |
| newblock->next = currblock->next; |
| currblock->next = newblock; |
| } |
| return newblock; //new block |
| } |
| |
| |
| /********************************************************************** |
| * MEMBLOCK::find_chunk |
| * |
| * Find a chunk within the block which is big enough for the given request |
| **********************************************************************/ |
| |
| MEMUNION *MEMBLOCK::find_chunk( //find free chunk |
| inT32 count //size required |
| ) { |
| MEMUNION *chunk; //current chunk |
| inT32 chunksize; //size of free chunk |
| MEMUNION *chunkstart; //start of free chunk |
| inT32 spaceshift; //shift in lowerspace |
| |
| if (upperspace <= lowerspace) { |
| freechunk = blockstart; //restart chunklist |
| upperspace += lowerspace; |
| lowerspace = 0; //correct space counts |
| } |
| chunk = freechunk; //current free chunk |
| if (chunk->size < count) { //big enough? |
| spaceshift = 0; |
| do { |
| while (chunk->size < 0) { //find free chunk |
| chunk -= chunk->size; //skip forward |
| if (chunk == blockend) { |
| chunk = blockstart; //restart block |
| //gone back to start |
| spaceshift = -lowerspace; |
| } |
| if (chunk == freechunk) |
| return chunk; //gone all round & failed |
| } |
| chunkstart = chunk; //start of chunk |
| chunksize = chunk->size;; |
| chunk += chunk->size; |
| while (chunk != blockend //until end |
| && chunk->size > 0) { //or used |
| chunksize += chunk->size;//coalesce free blocks |
| //gone all round |
| if (chunk == freechunk) { |
| //ensure it is at end |
| freechunk += chunk->size; |
| upperspace -= chunk->size; |
| lowerspace += chunk->size; |
| spaceshift -= chunk->size; |
| } |
| if (chunk == topchunk) //got back to end one |
| topchunk = chunkstart; //end one bigger |
| chunk += chunk->size; //get next block |
| } |
| //new big block |
| chunkstart->size = chunksize; |
| if (chunksize < count) |
| spaceshift += chunksize; //skipping free block |
| if (chunk == blockend) { |
| chunk = blockstart; //back to start |
| if (freechunk == blockend) { |
| freechunk = blockstart;//so is freechunk |
| upperspace += lowerspace; |
| lowerspace = 0; |
| spaceshift = 0; |
| } |
| else |
| //so is shift |
| spaceshift = -lowerspace; |
| } |
| } |
| while (chunksize < count && chunk != freechunk); |
| if (chunksize < count) |
| return chunk; //failed |
| lowerspace += spaceshift; //get space counts right |
| upperspace -= spaceshift; |
| freechunk = chunkstart; |
| return chunkstart; //success |
| } |
| return chunk; //easy |
| } |
| |
| |
| #ifdef __UNIX__ |
| /********************************************************************** |
| * trace_caller |
| * |
| * Return the return address of the caller at a given depth back. |
| * 0 gives the return address of the caller to trace_caller. |
| * S300 ONLY!! |
| **********************************************************************/ |
| //#pragma OPTIMIZE OFF /*force link*/ |
| |
| void *trace_caller( //trace stack |
| inT32 depth //depth to trace |
| ) { |
| #ifdef hp9000s800 |
| |
| unsigned sp, pc, rp; //registers |
| UWREC rec1; //for unwinder |
| UWREC rec2; |
| |
| sp = (unsigned) (&depth + 9); |
| pc = *(int *) (sp - 20); |
| rp = 0; |
| get_pcspace(&rec1, pc); |
| rec1.cur_frsize = 0xc0; |
| rec1.currlo = pc & ~3; |
| rec1.curdp = 0; |
| rec1.toprp = rp; |
| |
| while (depth > 0) { |
| if (U_get_previous_frame (&rec1, &rec2)) |
| return NULL; |
| rec1.currlo = rec2.currlo; |
| rec1.cur_frsize = rec2.cur_frsize; |
| rec1.cursp = rec2.cursp; |
| rec1.currls = rec2.currls; |
| rec1.curdp = rec2.curdp; |
| depth--; |
| } |
| return (void *) rec1.currlo; |
| #else |
| void *a6; //address register |
| |
| a6 = &depth - 2; |
| while (depth > 0) { |
| a6 = *(void **) a6; //follow chain |
| depth--; |
| } |
| return *((void **) a6 + 1); |
| #endif |
| } |
| |
| |
| //#pragma OPTIMIZE ON |
| |
| #else |
| |
| // Fake procedure for non-UNIX |
| void *trace_caller( //trace stack |
| inT32 depth //depth to trace |
| ) { |
| return NULL; |
| } |
| #endif |
| |
| /********************************************************************** |
| * identify_struct_owner |
| * |
| * Get an index into the table of owners of structures. |
| * Implemented very inefficiently, but only a debug tool! |
| **********************************************************************/ |
| |
| inT32 identify_struct_owner( //get table index |
| inT32 struct_count, //cell size |
| const char *name //name of type |
| ) { |
| inT32 index; //index to structure |
| |
| for (index = 0; index < name_counts[struct_count] |
| && strcmp (name, owner_names[struct_count][index]); index++); |
| if (index < MAX_CLASSES) { |
| if (index == name_counts[struct_count]) { |
| name_counts[struct_count]++; |
| owner_names[struct_count][index] = name; |
| owner_counts[struct_count][index] = 0; |
| } |
| } |
| return index; |
| } |
| |
| |
| /********************************************************************** |
| * check_struct |
| * |
| * Check a particular structure size for consistency. |
| **********************************************************************/ |
| |
| void check_struct( //check a structure |
| inT8 level, //print control |
| inT32 count //no of bytes |
| ) { |
| MEMUNION *element; //current element |
| MEMUNION *block; //current block |
| inT32 struct_count; //no of required structs |
| inT32 block_count; //no of structure blocks |
| inT32 free_count; //size of freelist*/ |
| inT32 name_index; //named holder |
| inT32 named_total; //total held by names |
| |
| //no of MEMUNIONS-1 |
| struct_count = (count - 1) / sizeof (MEMUNION); |
| if (struct_count < 0 || struct_count >= MAX_STRUCTS) |
| //request too big |
| MEMTOOBIG.error ("check_struct", ABORT, "%d", (int) count); |
| |
| free_count = 0; //size of freelist |
| //count blocks |
| for (block_count = 0, block = struct_blocks[struct_count]; block != NULL; block = block->ptr, block_count++); |
| if (block_count > 0) { |
| //scan freelist |
| for (element = free_structs[struct_count]; element != NULL; element = element->ptr) |
| free_count++; |
| if (level >= MEMCHECKS) { |
| tprintf ("No of structs of size %d in use=%d,", |
| (int) count, (int) structs_in_use[struct_count]); |
| tprintf (" %d free", free_count); |
| tprintf (" in %d blocks, total space=%d\n", |
| (int) block_count, |
| block_count * STRUCT_BLOCK_SIZE * sizeof (MEMUNION)); |
| for (named_total = 0, name_index = 0; |
| name_index < name_counts[struct_count]; name_index++) { |
| tprintf ("No held by %s=%d\n", |
| owner_names[struct_count][name_index], |
| owner_counts[struct_count][name_index]); |
| named_total += owner_counts[struct_count][name_index]; |
| } |
| tprintf ("Total held by names=%d\n", named_total); |
| } |
| } |
| if (structs_in_use[struct_count] + free_count |
| != block_count * (STRUCT_BLOCK_SIZE / (struct_count + 1) - 1)) |
| BADSTRUCTCOUNT.error ("check_struct", ABORT, "%d+%d=%d", |
| structs_in_use[struct_count], free_count, |
| block_count * (STRUCT_BLOCK_SIZE / |
| (struct_count + 1) - 1)); |
| } |
| |
| |
| /********************************************************************** |
| * check_structs |
| * |
| * Reports statistics on each maintained structure type by calling |
| * free_struct(NULL) on each. Only active structure types are reported. |
| **********************************************************************/ |
| |
| void check_structs( //count in use on structs |
| inT8 level //print control |
| ) { |
| inT8 index; //index to structs |
| |
| for (index = 1; index <= MAX_STRUCTS; index++) |
| //check number allocated |
| check_struct (level, index * sizeof (MEMUNION)); |
| } |
| |
| |
| /********************************************************************** |
| * new_struct_block |
| * |
| * Allocate space for a new block of structures. The space is obtained |
| * from alloc_mem, and a freelist of such blocks is maintained for when |
| * the individual structure types get completely freed. |
| **********************************************************************/ |
| |
| void *new_struct_block() { //allocate memory |
| MEMUNION *element; //current element |
| MEMUNION *returnelement; //return value |
| |
| returnelement = free_block; |
| if (returnelement == NULL) { |
| //need a new block |
| element = |
| (MEMUNION *) alloc_mem_p (STRUCT_BLOCK_SIZE * sizeof (MEMUNION)); |
| if (element == NULL) |
| return NULL; //can't get more |
| returnelement = element; //going to return 1st |
| } |
| else { |
| //new free one |
| free_block = returnelement->ptr; |
| } |
| return returnelement; //free cell |
| } |
| |
| |
| /********************************************************************** |
| * old_struct_block |
| * |
| * Free memory allocated by new_struct_block. The block is returned |
| * to a freelist ready for a new call to new_struct_block. |
| * This saves confusion over freeing "permanent" blocks, yet |
| * allows them to be recycled for different structures. |
| **********************************************************************/ |
| |
| void old_struct_block( //free a structure block |
| MEMUNION *deadblock //block to free |
| ) { |
| if (deadblock != NULL) { |
| deadblock->ptr = free_block; //add to freelist |
| free_block = deadblock; |
| free_struct_blocks++; |
| } |
| if (free_struct_blocks > MAX_FREE_S_BLOCKS) { |
| MEMUNION *next_block; //next in list |
| deadblock = free_block; |
| do { |
| next_block = deadblock->ptr; |
| free_mem(deadblock); //really free it |
| deadblock = next_block; |
| } |
| while (deadblock != NULL); |
| free_struct_blocks = 0; |
| free_block = NULL; |
| } |
| } |