/* $Header: $ */ /* * A portable, fast heap manager. * * This implementation is directly derived from the article in which this * algorithm first appeared ; * * "Efficient Implementation of the First-Fit Strategy for Dynamic * Storage Allocation" * R. P. Brent, Australian National University * ACM Transactions on Programming Languages and Systems * Volume 11, No. 3, July 1989 pp 388-403. * * $Log: $ */ /* *-IMPORTS: * <common/ccompat.h> * __CONST__ * __USE_PROTO__ * __ARGS () * <common/xdebug.h> * __LOCAL__ * <common/tricks.h> * IS_POWER_OF_TWO * <stddef.h> * NULL */ #include <common/ccompat.h> #include <common/xdebug.h> #include <common/tricks.h> #include <stddef.h> #define _ST_IMPL 1 #include <kernel/st_alloc.h> #ifndef _ST_ASSERT # define _ST_ASSERT 1 #endif #ifndef _ST_ALLOC # define _ST_ALLOC 1 #endif #ifndef _ST_FREE # define _ST_FREE 1 #endif #ifndef _ST_REALLOC # define _ST_REALLOC 1 #endif #ifndef _ST_INIT # define _ST_INIT 1 #endif #ifndef _ST_MAXAVAIL # define _ST_MAXAVAIL 1 #endif /* * Since this code can be used in an embedded environment, we may not want to * use the C library version of assert () to report errors. On the other hand, * in a hosted environment we might. */ #if _HOSTED #include <assert.h> #define ASSERT(x) assert (x) #else #include <sys/debug.h> #endif /* * Set up default fast-copy routines for various environments. */ #include <string.h> #define _ST_COPY(dest, src, words) \ (void) memcpy (dest, src, (words) * sizeof (_ST_WORD_T)) /* * In several places in the code we need to adjust pointer values by +1 or -1 * to account for the space taken up the by the links between adjacent * blocks, allocated or not. * * Here we define some private macros to help deal with this. While it is * correct and reasonably documented as it stands, we will likely want to * implement a variant algorithm that does not keep allocated blocks on a * list. This should reduce both search time and space overhead, with the * expense of requiring the user to supply the original block size. For now, * we pay both penalties. */ #define _ST_WORD_S sizeof (_ST_WORD_T) #define _ST_AHDR_SIZE 1 /* word size of an allocated header */ #define _ST_BYTE2WORD(s) ((s + (1 + _ST_AHDR_SIZE) * _ST_WORD_S - 1) / \ _ST_WORD_S) /* * _ST_BYTE2WORD () converts a size * passed in by a client into a block * word size, which includes space * for the allocated-block header. */ #define _ST_WORD2BYTE(s) ((s - _ST_AHDR_SIZE) * _ST_WORD_S) /* * _ST_WORD2BYTE () reverses the * mapping given by _ST_BYTE2WORD (). */ #define _ST_ADDR2PTR(q,a) ((__VOID__ *) a) /* * Mapping from an _ST_ADDR_T to a * user pointer to real memory. */ #define _ST_PTR2ADDR(q,a) ((_ST_WORD_T *) a) /* * Mapping from a user pointer to * an _ST_ADDR_T. */ #define _ST_BUCKET_BASE(q,b) (q->_arena_base + (b) * q->_words_per_bucket) #if _ST_ASSERT /* * This function does some general checks on the consistency of the contents * of the _ST_HEAP_CONTROL block passed as "q". If the contents of the control * block (and the auxiliary arrays which are part of it) do not pass muster, * then the _qheap_error member of the control block is set, and the function * returns an error indication. * * The return value is 0 if the control block tested OK, -1 if it did not. */ #if __USE_PROTO__ int (st_assert) (_ST_HEAP_CONTROL_P q) #else int st_assert __ARGS ((q)) _ST_HEAP_CONTROL_P q; #endif { _ST_ADDR_T scan; int count, cntl, bucket; scan = _ST_BUCKET_BASE (q, 0); for (bucket = 0 ; bucket < q->_buckets_inuse ; bucket ++) { _ST_ADDR_T end; int max; if ((end = _ST_BUCKET_BASE (q, bucket + 1)) > q->_arena_end) end = q->_arena_end; /* * Verify that _ST_HEAP_FIRST () points within the bucket, * to the current value of scan if there are blocks in this * bucket. */ if (scan < end && _ST_HEAP_FIRST (q, bucket) != scan) { _ST_SET_ERROR (q, "Bad first block pointer in bucket"); return -1; } else if (scan >= end && _ST_HEAP_FIRST (q, bucket) < end) { _ST_SET_ERROR (q, "First block pointer should be pointing past bucket end"); return -1; } /* * Walk over the contents of the bucket to verify the maximum * block size within the bucket. We also check here for * consecutive unmerged free blocks. */ count = max = 0; while (scan < end) { cntl = _ST_BLOCK_CONTROL (q, scan); if (_ST_BLOCK_FREE (cntl)) { /* block is free */ if (cntl > max) max = cntl; if (++ count > 1) { _ST_SET_ERROR (q, "Adjacent free blocks not merged"); return -1; } } else /* block in use */ count = 0; if (_ST_BLOCK_SIZE (cntl) > q->_arena_size) { _ST_SET_ERROR (q, "Link to next block invalid"); return -1; } scan = _ST_HEAP_NEXT (scan, cntl); } if (_ST_HEAP_BIGGEST (q, bucket + q->_buckets_inuse) != max) { _ST_SET_ERROR (q, "Incorrect maximum free block size"); return -1; } /* * Check for unmerged free blocks between bucket boundaries. */ if (count != 0 && scan < q->_arena_end && _ST_BLOCK_FREE (_ST_BLOCK_CONTROL (q, scan))) { _ST_SET_ERROR (q, "Unmerged free blocks between block boundaries"); return -1; } } if (scan != q->_arena_end) { _ST_SET_ERROR (q, "Last block too large!"); return -1; } return 0; } #if TRACER #include <sys/cmn_err.h> #if __USE_PROTO__ int st_check (_ST_HEAP_CONTROL_P q, __CONST__ char * where) #else int st_check (q, where) _ST_HEAP_CONTROL_P q; __CONST__ char * where; #endif { if (st_assert (q)) { cmn_err (CE_WARN, "kernel heap bad: %s", _ST_HEAP_ERROR (q)); return -1; } return 0; } #endif /* TRACER */ #endif /* _ST_ASSERT */ #if _ST_ALLOC /* * st_double () doubles the depth of the index heap, assuming that * _buckets_inuse < _buckets_maximum / 2 * * For internal use only. */ #if __USE_PROTO__ __LOCAL__ void (st_double) (_ST_HEAP_CONTROL_P q) #else __LOCAL__ void st_double __ARGS ((q)) _ST_HEAP_CONTROL_P q; #endif { int i,k; ASSERT (q->_buckets_inuse * 2 <= q->_buckets_maximum); /* * The new buckets don't have any blocks starting within them, so * set the base address over the top. */ k = q->_buckets_inuse; for (i = 0 ; i < k ; i ++) q->_bucket_first [i + k] = q->_arena_end; /* * The bucket size heap needs to be expanded by copying the maximum * sizes down for the preexisting buckets, and inserting zeroes for * the new buckets. * eg max(b1,b2) b1 b2 * becomes max(b1,b2) max(b1,b2) 0 b1 b2 0 0 */ for (; k > 0 ; k >>= 1) for (i = 0 ; i < k ; i ++) { _ST_HEAP_BIGGEST (q, 2 * k + i) = _ST_HEAP_BIGGEST (q, k + i); _ST_HEAP_BIGGEST (q, 3 * k + i) = 0; } q->_buckets_inuse <<= 1; } /* * st_reduced () does the housekeeping necessary if a block with control word * at "a" has been allocated, or merged with a block on it's left in a * different segment. Note that we count on a block having been freed/ * allocated and having a valid control word. * * It should be that case that the _ST_HEAP_FIRST () has already been updated * in order for this routine to correctly calculate the sizes * * This implementation modifies this function, in that a test * if (BlockIsFree && CurrentMaximum > SizeOfNewBlock) * return; * from the C transliteration of the published algorithm has been moved out * to the callers of this function. This allows the callers to use a block * size value cached in their locals to perform the calculation, rather than * making this function fetch the control word for the block. This is NOT * an important size/speed optimisation (although parts of the original test * have been elided in the broken-out version), rather it allows algorithms * like realloc () considerably more latitude in the order in which things * are done. * * This function was called blfix1 () in the published algorithm. The original * algorithm passed in the block address, but since we have moved the check * discussed above outside, we only need the bucket number. */ #if __USE_PROTO__ void (st_reduced) (_ST_HEAP_CONTROL_P q, int bucket) #else void st_reduced __ARGS ((q, bucket)) _ST_HEAP_CONTROL_P q; int bucket; #endif { _ST_ADDR_T first, next; _ST_WORD_T max; #if 0 /* this test moved out to our callers */ /* * if (BlockIsFree && CurrentMaximum > SizeOfNewBlock) then there * is nothing to be done to segment "bucket". */ max = _ST_BLOCK_CONTROL (q, a); if (_ST_BLOCK_FREE (max) && _ST_HEAP_BIGGEST (q, bucket + q->_buckets_inuse) > \ _ST_BLOCK_SIZE (max)) return; #endif /* * Work out address of first block in "current" bucket, and the * base address of the "next" bucket. Note that we have to test to * see if "next" winds up past the end of space because the total * size of the arena may not divide evenly into buckets.. */ first = _ST_HEAP_FIRST (q, bucket); next = q->_arena_base + (bucket + 1) * q->_words_per_bucket; if (next >= q->_arena_end) next = q->_arena_end; /* * Calculate (by walking the block chain) the new maximum size free * block in this bucket, which may now be empty. */ max = 0; while (first < next) { /* There is a block starting in this bucket */ _ST_WORD_T cntl = _ST_BLOCK_CONTROL (q, first); ASSERT (cntl != 0); if (max < cntl) max = cntl; first = _ST_HEAP_NEXT (first, cntl); } /* * Now propagate the new maximum size information up the heap. */ bucket += q->_buckets_inuse; _ST_HEAP_BIGGEST (q, 0) = 0; /* sentinel */ while (_ST_HEAP_BIGGEST (q, bucket) > max) { int temp; _ST_HEAP_BIGGEST (q, bucket) = max; temp = _ST_HEAP_BIGGEST (q, _ST_BUCKET_SIBLING (bucket)); if (max < temp) max = temp; bucket = _ST_BUCKET_PARENT (bucket); } } /* * st_grown () does the housekeeping necessary after a block with control word * at "a" is freed, or merged with a block on its right, or created by * splitting (with "a" on the right of the split, in a different bucket than * the start of the original block that was split) * * This function was called blfix2 () in the published algorithm. */ #if __USE_PROTO__ void (st_grown) (_ST_HEAP_CONTROL_P q, _ST_ADDR_T a, int bucket) #else void st_grown __ARGS ((q, a, bucket)) _ST_HEAP_CONTROL_P q; _ST_ADDR_T a; int bucket; #endif { int max; ASSERT (bucket == _ST_HEAP_BUCKET (q, a)); /* * Expand the number of buckets in the heap if necessary. */ while (bucket >= q->_buckets_inuse) st_double (q); /* * This may be a new first free block. */ if (_ST_HEAP_FIRST (q, bucket) > a) _ST_HEAP_FIRST (q, bucket) = a; /* * Propagate changed block-size information upwards through heap. */ bucket += q->_buckets_inuse; max = _ST_BLOCK_CONTROL (q, a); _ST_HEAP_BIGGEST (q, 0) = max; /* sentinel */ while (_ST_HEAP_BIGGEST (q, bucket) < max) { _ST_HEAP_BIGGEST (q, bucket) = max; bucket = _ST_BUCKET_PARENT (bucket); } } /* * Returns the predecessor block to "a", which is guaranteed to exist thanks * to the dummy first block. * * If the address passed in does not belong to a valid block then the value * returned is the same value that was passed in, ie "a". */ #if __USE_PROTO__ _ST_ADDR_T (st_pred) (_ST_HEAP_CONTROL_P q, _ST_ADDR_T a, int bucket) #else _ST_ADDR_T st_pred __ARGS ((q, a, bucket)) _ST_HEAP_CONTROL_P q; _ST_ADDR_T a; int bucket; #endif { _ST_ADDR_T prev, scan; ASSERT (bucket == _ST_HEAP_BUCKET (q, a)); /* * If the passed-in block is the first in the bucket, then the * predecessor lives in the rightmost non-empty bucket to the left. */ if (_ST_HEAP_FIRST (q, bucket) == a) { /* * We walk the heap to find the nearest bucket on the left * that has a free block in it, as buckets that do not * contain free blocks may not contain a valid "first block" * entry, since due to block coalescence these entries may * refer to blocks which have been combined with others. * * st_pred () is the only code which is affected by this. */ bucket += q->_buckets_inuse; _ST_HEAP_BIGGEST (q, 0) = 1; /* boundary */ while (_ST_HEAP_BIGGEST (q, bucket - 1) == 0) bucket = _ST_BUCKET_PARENT (bucket); /* * Code here fixes two known "defects" in the original * published algorithm. * (i) In the case where "bucket" above could reach the root * node (eg, every word of storage had been previously * allocated), the "bucket" would go to zero and the * descend code would loop indefinitely. * (ii) In the case where storage in the left subtree of the * heap relative to the initial point is completely * allocated, the code to climb the heap will eventually * look into the next higher level of the heap once * "bucket" became a power of two (unless defect (i) * occurred instead) and as a result would descend into * the wrong part of the heap. * * We check here for "bucket" being a power of two, in which * case we snap the result of the search to the lowest bucket * on the left. */ if (! IS_POWER_OF_TWO (bucket)) { bucket --; while (bucket < q->_buckets_inuse) { bucket = 2 * bucket + 1; if (_ST_HEAP_BIGGEST (q, bucket) <= 0) bucket --; } bucket -= q->_buckets_inuse; } else bucket = 0; } /* * Either way, find the predecessor block by following the internal * block linkage within this bucket. */ scan = _ST_HEAP_FIRST (q, bucket); do { prev = scan; scan = _ST_HEAP_NEXT (scan, _ST_BLOCK_CONTROL (q, scan)); ASSERT (prev != scan); } while (scan < a); if (scan != a) { _ST_SET_ERROR (q, "Unable to find previous for block"); return a; /* flag error by returning same */ } return prev; } /* * Returns the index of a block of at least "size" words, or 0 if no such * block exists. */ #if __USE_PROTO__ __VOID__ * (st_alloc) (_ST_HEAP_CONTROL_P q, size_t size) #else __VOID__ * st_alloc __ARGS ((q, size)) _ST_HEAP_CONTROL_P q; size_t size; #endif { int n, bucket, cntl; _ST_ADDR_T scan; /* * Before we begin, convert the size_t passed in into a word count. * Note that we assume that the integer division of a size_t will * be optimised by the compiler into an appropriate number of right- * shifts (since a size_t is always unsigned, right?). * * We store the result into an integer because the free/used * comparison means we involve the sign bit. */ n = _ST_BYTE2WORD (size); /* includes header size */ /* * Since the node at the top of the size heap contains the size of * the largest available block, quickly determine whether or not * this request can be satisfied at all. */ if (_ST_HEAP_BIGGEST (q, 1) < n) return 0; /* * Now traverse the heap to find the first bucket containing a block * of sufficient size to satisfy the request. */ bucket = 1; while (bucket < q->_buckets_inuse) { bucket = 2 * bucket; if (_ST_HEAP_BIGGEST (q, bucket) < n) bucket ++; } bucket -= q->_buckets_inuse; /* * Now traverse the internal linkage within the bucket to find the * first block of the requisite size. */ scan = _ST_HEAP_FIRST (q, bucket); while ((cntl = _ST_BLOCK_CONTROL (q, scan)) < n) { ASSERT (cntl != 0); scan = _ST_HEAP_NEXT (scan, cntl); } /* * Now "scan" contains the index of the control word of the * desired block. */ _ST_BLOCK_SET_USED (q, scan, n); /* * The published algorithm used a variable here to hold the result * of a test on the basis that the call to st_grown () below might * change the result. Actually, st_grown () would not change the * result, but since st_grown () may call st_double () and alter * the offset of the leaf layer of the block heap, that would * invalidate his test. Since our "bucket" does not have that offset * built into it, we perform the test when needed. * * Note that we still perform st_grown () before st_reduced (), as * in the original. The reason for this was not explicated, but * appears to be because doing it in this order may reduce the * average number of heap nodes visited due to the particular heap * update termination conditions. */ /* * If necessary, split block; this may require a call to st_grown () * if the block created by the split is not in the same bucket. */ if (cntl > n) { _ST_ADDR_T next = _ST_HEAP_NEXT (scan, n); int next_bucket; _ST_BLOCK_SET_FREE (q, next, cntl - n); if ((next_bucket = _ST_HEAP_BUCKET (q, next)) > bucket) st_grown (q, next, next_bucket); } if (cntl == _ST_HEAP_BIGGEST (q, bucket + q->_buckets_inuse)) st_reduced (q, bucket); return _ST_ADDR2PTR (q, _ST_HEAP_NEXT (scan, _ST_AHDR_SIZE)); } #endif /* _ST_ALLOC */ #if _ST_FREE /* * Release a block of memory obtained using st_alloc (), where "a" is the * memory word index that was returned by st_alloc (). * * Note that this function comes in two flavours, depending on whether you * want clients to have to pass in the block-size. */ #if __USE_PROTO__ int (st_free) (_ST_HEAP_CONTROL_P q, __VOID__ * a ST_FREE_SIZE (size_t size)) #else int #ifdef USE_ST_SIZE st_free __ARGS ((q, a, size)) size_t size; #else st_free __ARGS ((q, a)) #endif _ST_HEAP_CONTROL_P q; __VOID__ * a; #endif { int bucket, cntl, temp; _ST_ADDR_T prev, next, addr; int reduce = 0; /* optimisation flag, see below */ addr = _ST_HEAP_NEXT_RAW (_ST_PTR2ADDR (q, a), - _ST_AHDR_SIZE); cntl = _ST_BLOCK_CONTROL (q, addr); if (_ST_BLOCK_FREE (cntl)) return -1; /* Block already free */ cntl = _ST_BLOCK_SIZE (cntl); #ifdef USE_ST_SIZE /* * As discussed in st_alloc (), we convert a passed-in byte count * into a word count to isolate the clients from the notion of * what we are using as a "word". * * The expression we want to test is * cntl == ceil (size / sizeof (_ST_WORD_T)) + 1 * where the + 1 factor is for the block size header. */ if (cntl != _ST_BYTE2WORD (size)) return -2; /* Block size mismatch */ #endif bucket = _ST_HEAP_BUCKET (q, addr); /* * Locate the previous block. Note that we attempt this operation * considerably earlier than we really need to; this is done since * it is the only really reliable way of verifying that the address * given to this routine really does belong to a block that was * allocated with st_alloc (). * * (Note that the above statement is only true for versions of the * algorithm that maintain allocated blocks on the block list) */ if ((prev = st_pred (q, addr, bucket)) == addr) return -3; /* not a valid block */ /* * Now that we have performed some sanity checks, free the block. */ _ST_BLOCK_SET_FREE (q, addr, cntl); /* * Check the next rightmost block to see if we should merge with it. */ next = _ST_HEAP_NEXT (addr, cntl); if (next < q->_arena_end && _ST_BLOCK_FREE (temp = _ST_BLOCK_CONTROL (q, next))) { /* * Merge the new block with its immediate neighbour on the * right. Note that we elide the _ST_BLOCK_SIZE () of temp * immediately below because _ST_BLOCK_SET_FREE masks the * third argument anyway. */ cntl += temp; _ST_BLOCK_SET_FREE (q, addr, cntl); temp = _ST_HEAP_BUCKET (q, next); /* * Do we need to recalculate the maximum block size * for the block that "next" is in ? Yes, iff * heap_biggest (temp) == block_size (next). * We elide the call to _ST_BLOCK_SIZE below since * we know the block is free, hence needs no masking. */ reduce = _ST_HEAP_BIGGEST (q, temp + q->_buckets_inuse) == _ST_BLOCK_CONTROL (q, next); ASSERT (_ST_HEAP_BIGGEST (q, temp + q->_buckets_inuse) >= _ST_BLOCK_CONTROL (q, next)); if (temp > bucket) { _ST_HEAP_FIRST (q, temp) = _ST_HEAP_NEXT (addr, cntl); if (reduce) { st_reduced (q, temp); reduce = 0; } } else ASSERT (_ST_HEAP_FIRST (q, temp) != next); } /* * Check the next leftmost block to see if we should merge with it. */ /* * Optimisation note: the published version of the algorithm * does a normal call to st_reduced () below. However, since we * are freeing a block, we note that the only circumstance where * this will be at all necessary is when the block we are freeing * was merged with a block on it's right which was the previous * largest free block (or at least the same size). We can thus * reduce the number of calls to st_reduced () by putting in an * extra guard condition with a flag set above. * * Is this worth the effort? Let's profile it and see. */ if (_ST_BLOCK_FREE (temp = _ST_BLOCK_CONTROL (q, prev))) { cntl += temp; _ST_BLOCK_SET_FREE (q, prev, cntl); /* * If we are merging with a block in a previous bucket, then * we must adjust our "first block" and call st_reduced () to * update the size heap. */ if (_ST_HEAP_FIRST (q, bucket) == addr) { _ST_HEAP_FIRST (q, bucket) = _ST_HEAP_NEXT (prev, cntl); /* * As discussed above, we call st_reduced () iff * we have merged with a block on the right of a * size that indicates recomputing is necessary. */ if (reduce) { ASSERT (_ST_HEAP_BIGGEST (q, bucket + q->_buckets_inuse) <= _ST_BLOCK_CONTROL (q, addr)); st_reduced (q, bucket); } /* * We cannot incrementally update "bucket" since * "prev" may refer to address a number of buckets * prior to "addr". */ bucket = _ST_HEAP_BUCKET (q, prev); } st_grown (q, prev, bucket); } else if (cntl > _ST_HEAP_BIGGEST (q, bucket + q->_buckets_inuse)) { /* * The total size of the newly freed block exceeds the * previous maximum block size of the current bucket, so call * st_grown () to update the size heap. * * Note that st_alloc () calls st_grown () without performing * the size test like we do, because st_alloc () may want the * heap size doubled. Here, that cannot happen, so we avoid * the call. */ st_grown (q, addr, bucket); } return 0; } #endif /* _ST_FREE */ #if _ST_REALLOC #ifndef _ST_COPY /* * Helper function which supplies a default block-copy routine for the * st_realloc () function in case there is no special-purpose copy routine * in the target environment. */ #if __USE_PROTO__ __LOCAL__ void (_ST_COPY) (_ST_WORD_T * dest, __CONST__ _ST_WORD_T * src, size_t copywords) #else __LOCAL__ void _ST_COPY __ARGS ((dest, src, copywords)) _ST_WORD_T * dest; _ST_WORD_T * src; size_t copywords; #endif { while (copywords --) * dest ++ = * src ++; } #endif /* ! defined (_ST_COPY) */ #define _ST_BLOCK_COPY(q,d,s,n) \ _ST_COPY (_ST_HEAP_ADDR (q, d), _ST_HEAP_ADDR(q, s), n) /* * Request that the block of memory at address "a" be grown (or shrunk) in * size to "newsize" bytes (possibly from "oldsize" bytes). The returned * value is the base address of the new block of memory, which may be * different than the old address. If the block was moved, then the contents * are guarenteed to be preserved bit-for-bit, but the client must take * responsibility for relocating pointers. * * If it is not possible for the allocator to either grow or relocate the * block due to a lack of space, st_realloc () returns 0, which for us can * never be a valid return address. In this case, the original block has been * left untouched. * * Note that this function comes in two flavours, depending on whether you * want clients to have to pass in the block-size. * * -------------------------------------------------------------------------- * * Design note: there are many, many ways that this function could be * implemented, depending on how sensitive you are to issues of code size, * execution speed, or arena space efficiency. * * The possible checks are (in order of execution time reduction) * (i) Check to see if the block can be extended upwards, * (ii) Find the previous block to see if it can be extended downwards, * (iii) See if we can grow both down and up to fulfil the request, * (iv) Find any other block of sufficient size to fulfil the request. * * The primary criterion I have set for this routine is that it must always * succeed in finding memory to satisfy the request. Note that the number of * cases that this produces as a result is extremely large, but that it seems * better to fulfil a user's request than to fail it simply on the basis that * we want the common case to be fast. * * For now, we'll prefer not to copy, and reduce our code size by just * relying on st_alloc () and st_free () for the worst case... but be aware * that it might be a good idea to set a "realloc mode" in the heap control * block that specifies how we order operations in the case where we break * out the details of new () and disp (). */ #if __USE_PROTO__ __VOID__ * (st_realloc) (_ST_HEAP_CONTROL_P q, __VOID__ * a, size_t newsize ST_FREE_SIZE (size_t oldsize)) #else __VOID__ * #ifdef USE_ST_SIZE st_realloc __ARGS ((q, a, newsize, oldsize)) size_t oldsize; #else st_realloc __ARGS ((q, a, newsize)) #endif _ST_HEAP_CONTROL_P q; __VOID__ * a; size_t newsize; #endif { int bucket, cntl, prev_reduce, next_bucket, next_first; int delta; /* block size change in words */ _ST_ADDR_T prev, addr, next; addr = _ST_HEAP_NEXT_RAW (_ST_PTR2ADDR (q, a), -1); cntl = _ST_BLOCK_CONTROL (q, addr); if (_ST_BLOCK_FREE (cntl)) return (_ST_ADDR_T) -1; /* Block is free ! */ cntl = _ST_BLOCK_SIZE (cntl); /* work with free units */ /* * Before we begin, let's convert newsize (and optionally oldsize) * to word counts from byte counts. This may mean that both round * to the same word count and we don't need to do anything (big win!) */ newsize = _ST_BYTE2WORD (newsize); #ifdef USE_ST_SIZE if (cntl != _ST_BYTE2WORD (oldsize)) return (_ST_ADDR_T) -2; /* Block size mismatch */ #endif if ((delta = newsize - cntl) == 0) return a; /* Already done! What service! */ bucket = _ST_HEAP_BUCKET (q, addr); /* * Locate the previous block, so that we can see if we can grow * down into it. Note that in the variant system where allocated * blocks are not part of a chain (in order to save space) that by * finding the previous free block we achieve an equivalent result, * since we cannot grow down unless we have an adjacent free block. * * Note also that in the case where allocated blocks are not * chained, finding the previous free block is a prerequisite to * finding the subsequent free block. */ if ((prev = st_pred (q, addr, bucket)) == addr) return (_ST_ADDR_T) -3; /* not a valid block */ /* * Now that we have performed some sanity checks, look to see how * we should grow the block. */ /* * Check the next rightmost block to see if we can grow into it. * If we are shrinking the allocation, this is trivially true. * * To save ourselves a local, we re-use "bucket" here as the amount * of free space in the next rightmost block. We need to keep this * value for the next test so that we can expand both up and down * if necessary. */ bucket = 0; prev_reduce = 0; if ((next = _ST_HEAP_NEXT (addr, cntl)) < q->_arena_end && _ST_BLOCK_FREE (cntl = _ST_BLOCK_CONTROL (q, next))) bucket = cntl; /* available adjacent words */ if (bucket >= delta) { /* * Whether we are growing or shrinking, we have enough room. * Set the block's new size, and skip to the common code below * which adjusts the right edge of a block by "delta" words. */ ASSERT (next < q->_arena_end || (next == q->_arena_end && delta < 0)); _ST_BLOCK_SET_USED (q, addr, newsize); /* * Go to the common exit sequence for in-place adjustment, * after setting "prev" to be the base of the block that we * will return to the user. */ prev = addr; goto adjust_right; } /* * Check the next leftmost block to see if we should move down. Note * that we add in the free size of the right block to the calculation * so that we can expand both down and up to fill space. * * Given that we have to copy, however, we'll drop to the bottom of * the available room. */ if (_ST_BLOCK_FREE (cntl = _ST_BLOCK_CONTROL (q, prev)) && (cntl + bucket) >= delta) { /* * Well, we have enough space. Now, let's make "delta" equal * to the amount by which we need to adjust the block on * the right. */ delta -= cntl; /* * We set the "prev" block as used now for st_reduced (). */ _ST_BLOCK_SET_USED (q, prev, newsize); /* * Since we are vaporising the original block at "addr", we * should deal with checking to see if it was the first block * in it's bucket, otherwise we may wind up with a dangling * pointer. For now, we just point it to the successor to * "addr", and if this this does not turn out to be correct, * the common code to move the LHS below will do the right * thing. */ if (_ST_HEAP_FIRST (q, (bucket = _ST_HEAP_BUCKET (q, addr))) == addr) _ST_HEAP_FIRST (q, bucket) = next; /* * If we want to move the block down, then we should update * the heap information related to the block we are moving * into before we move into it. * * Everything relating to what happens to the right edge of * the block will be dealt with below, so all we have to do * here is determine whether to call st_reduced (). * * We don't actually call st_reduced () here, since until the * left edge has been dealt with there can be a temporary * loss of block connectivity. */ bucket = _ST_HEAP_BUCKET (q, prev); if (_ST_HEAP_BIGGEST (q, bucket + q->_buckets_inuse) == cntl) prev_reduce = 1; /* * Now, copy the original data from the block at "addr". How * much to copy? Re-fetch the block size from the control * word for now, although this may have to change to use * "oldsize" if this is changed to use a variant algorithm * where allocated blocks are not part of a list. * * How to perform the copy? Each target system will probably * have it's own routine for performing a word-aligned, word- * counted, upward-only high-speed block copy. Here, we * request the services of that routine, with a default * provided just prior to this code in case there is no * special facility for that purpose. * * Note that we perform the move now because there is no way * that the move can invalidate the block header (if any) in * the rightmost block. We know this because the size that we * are copying by is the size of the original block, and we * are moving down, so we can't write over anything after the * original block. */ _ST_BLOCK_COPY (q, _ST_HEAP_NEXT (prev, _ST_AHDR_SIZE), _ST_HEAP_NEXT (addr, _ST_AHDR_SIZE), _ST_BLOCK_SIZE (_ST_BLOCK_CONTROL (q, addr)) -\ _ST_AHDR_SIZE); /* * Now, do the common part. */ ASSERT (_ST_HEAP_NEXT (next, delta) == _ST_HEAP_NEXT (prev, newsize)); goto adjust_right; } /* * We have tried to move the block more-or-less in place, and the * some of the block and both neighbours does not yield enough space, * so we try and realloc () the naive way, using st_alloc () and * st_free (). We re-use these routines and just adjust for the fact * that they measure sizes in bytes rather than duplicating the code. * * Please note that the "prev" returned by st_alloc () has already * had the adjustment by 1 word to skip over the control block. */ if ((prev = _ST_PTR2ADDR (q, st_alloc (q, _ST_WORD2BYTE (newsize)))) == 0) return 0; /* * Do the block copy of the original contents and release them. * See the discussion above on the block copier. * * Note that we pass the address "a" into st_free (), as "addr" is * adjusted to point at the control word. */ _ST_BLOCK_COPY (q, prev, _ST_HEAP_NEXT (addr, _ST_AHDR_SIZE), _ST_BLOCK_SIZE (_ST_BLOCK_CONTROL (q, addr)) - _ST_AHDR_SIZE); if (st_free (q, a ST_FREE_SIZE (oldsize)) != 0) { /* * What the duece! Throw the bums out on their ears! */ ASSERT (1 == 0); } return _ST_ADDR2PTR (q, prev); /* * Perform the necessary adjustments to the index heap for the * event that is happening on the right-hand side of the original * block. The block boundary on the right is being moved by "delta" * words from it's *original* location, ie the block at "next" is * being either grown by -delta or shrunk by delta, or remaining * unaffected. If next is being shrunk, then we know at this point * that "next" is free. If it's being grown, then "next" could be * either free or allocated, so we may have to create a new block. */ adjust_right: /* * Common case: fetch the current statistics of the "next" block and * set us up so that we point at the location of the new block. */ if (next < q->_arena_end) { cntl = _ST_BLOCK_CONTROL (q, next); bucket = _ST_HEAP_BUCKET (q, next); /* * We record whether or not "next" has an _ST_HEAP_FIRST () * pointer looking at it so we can update it below depending * on how we move things around. */ next_first = _ST_HEAP_FIRST (q, bucket) == next; } else { /* * Treat the end of the world as an allocated block. */ cntl = -1; next_first = bucket = 0; } next = _ST_HEAP_NEXT (next, delta); next_bucket = _ST_HEAP_BUCKET (q, next); if (delta > 0) { /* eat into "next", which is free */ /* * If we need to, make the _ST_HEAP_FIRST () pointer track * the block. Note that there is no harm in this if "next" * actually lives a few buckets along, in fact it's required * that if there's no blocks in the bucket, _ST_HEAP_FIRST () * must point beyond the end of the bucket. */ if (next_first) _ST_HEAP_FIRST (q, bucket) = next; /* * Grow up into our neighbour. This case is basically * identical to the regular allocation algorithm except that * we don't have to reserve space for a new control word. * * The comments in st_alloc () also apply here, see above. * * Note that since we know that "next" is free, we also know * that "cntl" is a positive integer and doesn't need masking. */ if (cntl > delta) { /* * Create a free block, which may cause a call to * st_grown () if it begins in another bucket. Like * st_alloc (), we call st_grown () whether or not * it needs to propagate size information up the * index heap, because we may require that the size * of the index heap be doubled as a side-effect of * the overflow into the next bucket. */ _ST_BLOCK_SET_FREE (q, next, cntl - delta); if (next_bucket > bucket) st_grown (q, next, next_bucket); } else { /* * In this case, "next" has been completely eaten * up, so we just let the common code below deal with * shrinking the "biggest free". * * I make lots of gratuitous assertions, don't I? */ ASSERT (cntl == delta); } /* * Regardless of whether we have created a new block, we * should see whether we should adjust the "largest free" * for the bucket, according to the usual rules. */ if (_ST_HEAP_BIGGEST (q, bucket + q->_buckets_inuse) == cntl) st_reduced (q, bucket); } else if (delta < 0) { /* "-delta" free words below "next" */ /* * This code is the inverse of the above, except that we * may not be affecting the actual "next" block if it is * in use. * * Note that in this scenario, "next_bucket" has the inverse * of the normal relation to "bucket", since it may be below * "bucket". */ _ST_BLOCK_SET_FREE (q, next, (_ST_BLOCK_FREE (cntl) ? cntl : 0) - delta); if (next_bucket < bucket) { _ST_HEAP_FIRST (q, bucket) = _ST_HEAP_NEXT (next, _ST_BLOCK_CONTROL (q, next)); /* * We don't want to call st_reduced () if the original * "next" block wasn't free. It won't hurt things, * but it wastes time. However, since "cntl" is * negative in that case, the test below will always * fail in that situation. */ if (_ST_HEAP_BIGGEST (q, bucket + q->_buckets_inuse) == cntl) st_reduced (q, bucket); } if (_ST_BLOCK_CONTROL (q, next) > _ST_HEAP_BIGGEST (q, next_bucket + q->_buckets_inuse)) st_grown (q, next, next_bucket); } if (prev_reduce != 0) st_reduced (q, _ST_HEAP_BUCKET (q, prev)); return _ST_ADDR2PTR (q, _ST_HEAP_NEXT (prev, 1)); } #endif /* _ST_REALLOC */ #if _ST_INIT /* * Initialize a heap with fine control over parameter settings. */ #if __USE_PROTO__ void (st_ctor) (_ST_HEAP_CONTROL_P q, int segs, size_t arensize, _ST_ADDR_T arenabase) #else void st_ctor __ARGS ((q, segs, arensize, arenabase)) _ST_HEAP_CONTROL_P q; int segs; size_t arensize; _ST_ADDR_T arenabase; #endif { q->_buckets_maximum = segs; q->_arena_size = arensize; q->_arena_base = (_ST_ADDR_T) arenabase; q->_arena_end = _ST_HEAP_NEXT (arenabase, arensize); q->_bucket_biggest = (_ST_WORD_T *) (q + 1); q->_bucket_first = (_ST_ADDR_T *) (q->_bucket_biggest + segs * 2); /* * Perform regular initialisation. */ q->_heap_error = NULL; q->_buckets_inuse = 1; q->_words_per_bucket = (q->_arena_size + q->_buckets_maximum - 1) / q->_buckets_maximum; _ST_HEAP_BIGGEST (q, 1) = q->_arena_size; _ST_HEAP_FIRST (q, 0) = q->_arena_base; _ST_BLOCK_SET_FREE (q, q->_arena_base, q->_arena_size); /* * Create dummy sentinel block. */ st_alloc (q, 0); } /* * Simplified initialization code. Here we are just given a chunk of memory, * and we try to do our best with setting it up. * * For estimating the number of buckets, we take the square root of the heap * size as our starting point. To make this easy, we round the size up to the * nearest power of two and take the log, halve it and then exponentiate. Note * that by wrapping up the code below in a macro, we not only make it clearer * but we also get the extra level of macro rescanning necessary to expand the * _SIZE_T macro passed as the "type". * * I am just guessing that this is a good number of buckets, BTW. */ #define ST_SQRT(x, type) \ (1 << (__CONCAT (__MOST_BIT, type) (x) / 2 + 1)) #if __USE_PROTO__ _ST_HEAP_CONTROL_P (st_heap_init) (__VOID__ * memory, size_t size) #else _ST_HEAP_CONTROL_P st_heap_init __ARGS ((memory, size)) __VOID__ * memory; size_t size; #endif { size_t buckets; size_t overhead; _ST_HEAP_CONTROL_P q; buckets = size - 1; buckets = ST_SQRT (buckets, _SIZE_T); overhead = _ST_HEAP_CONTROL_SIZE (buckets); q = (_ST_HEAP_CONTROL_P) memory; memory = (__VOID__ *) ((char *) memory + overhead); size -= overhead; st_ctor (q, buckets, size / sizeof (_ST_WORD_T), (_ST_ADDR_T) memory); (void) _ST_CHECK (q, "st_heap_init ()"); return q; } #endif /* _ST_INIT */ #if _ST_MAXAVAIL /* * Return a size suitable for requesting the largest currently available * block of memory. * * Thanks to the way the index heap is constructed, this is in fact trivial. */ #if __USE_PROTO__ size_t (st_maxavail) (_ST_HEAP_CONTROL_P q) #else size_t st_maxavail __ARGS ((q)) _ST_HEAP_CONTROL_P q; #endif { return _ST_WORD2BYTE (_ST_HEAP_BIGGEST (q, 1)); } #endif /* _ST_MAXAVAIL */