blob: c79fc4d0dbc5cb52e3631ebcf2c54be840a55c42 [file] [log] [blame]
/**********************************************************************
* 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;
}
}