| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- /* Licensed under LGPLv2.1+ - see LICENSE file for details */
- #include "tiny.h"
- #include "bitops.h"
- #include <assert.h>
- #include <stdlib.h>
- #include <string.h>
- /* One byte header, one byte data. */
- #define MIN_BLOCK_SIZE 2
- /* Bit 7 (in any byte) == start or end. */
- #define TERM_BIT 0x80
- /* Bit 6 (first and last byte) == one byte long. */
- #define SINGLE_BYTE 0x40
- /* Bit 5 (of first byte) == "is this block free?" */
- #define FREE_BIT 0x20
- #define MAX_FREE_CACHED_SIZE 256
- /* Val is usually offset by MIN_BLOCK_SIZE here. */
- static unsigned encode_length(unsigned long val)
- {
- unsigned int bits = afls(val);
- /* 5 bits in first byte. */
- if (bits <= 5)
- return 1;
- /* 6 bits in last byte, 7 bits in middle ones. */
- return 2 + (bits - 5) / 7;
- }
- /* Header is included in length, so we might need an extra byte. */
- static unsigned encode_len_with_header(unsigned long len)
- {
- unsigned int hdrlen = 1;
- assert(len);
- while (encode_length(len + hdrlen - MIN_BLOCK_SIZE) != hdrlen)
- hdrlen = encode_length(len + hdrlen - MIN_BLOCK_SIZE);
- return hdrlen;
- }
- /* Encoding can be read from front or back; 0 is invalid at either
- * start or end. Returns bytes used for header. */
- static unsigned encode(unsigned long len, bool free, unsigned char arr[])
- {
- unsigned int hdrlen = 1;
- /* We can never have a length < MIN_BLOCK_SIZE. */
- assert(len >= MIN_BLOCK_SIZE);
- len -= MIN_BLOCK_SIZE;
- /* First byte always contains free bit. */
- arr[0] = TERM_BIT | (free ? FREE_BIT : 0);
- /* Also holds 5 bits of data (0 - 31) */
- arr[0] |= (len & 0x1F);
- len >>= 5;
- /* One byte only? */
- if (!len) {
- arr[0] |= SINGLE_BYTE;
- return hdrlen;
- }
- /* Middle bytes. */
- while (len >= (1 << 6)) {
- /* Next 7 data bits */
- arr[hdrlen++] = (len & 0x7F);
- len >>= 7;
- }
- arr[hdrlen++] = (len | TERM_BIT);
- return hdrlen;
- }
- /* Returns bytes used for header. */
- static unsigned decode(unsigned long *len, bool *free, const unsigned char *arr)
- {
- unsigned int hdrlen = 0, bits = 5;
- /* Free flag is in bit 5 */
- *free = (arr[hdrlen] & FREE_BIT);
- /* Bottom five bits are data. */
- *len = (arr[hdrlen] & 0x1f);
- if (!(arr[hdrlen++] & SINGLE_BYTE)) {
- /* Multi-byte encoding? */
- while (!(arr[hdrlen] & TERM_BIT)) {
- /* 7 more data bits. */
- *len |= (arr[hdrlen] & 0x7fUL) << bits;
- hdrlen++;
- bits += 7;
- }
- /* Final byte has 6 bits. */
- *len |= (arr[hdrlen] & 0x3fUL) << bits;
- hdrlen++;
- }
- *len += MIN_BLOCK_SIZE;
- return hdrlen;
- }
- /* We keep a helper array for freed mem, one byte per k. */
- static unsigned long free_array_size(unsigned long poolsize)
- {
- return poolsize / 1024;
- }
- /* We have series of 69 free sizes like so:
- * 1, 2, 3, 4. 6, 8, 10, 12, 14, 16. 20, 24, 28, 32... 252.
- */
- static unsigned long free_array_off(unsigned long size)
- {
- unsigned long off;
- if (size <= 4)
- off = size - 1;
- else if (size <= 16)
- off = size / 2 + 1;
- else
- off = size / 4 + 5;
- off *= 3;
- return off;
- }
- void tiny_alloc_init(void *pool, unsigned long poolsize)
- {
- /* We start with free array, and then the rest is free. */
- unsigned long arrsize = free_array_size(poolsize);
- /* Do nothing with 1 byte or less! */
- if (poolsize < MIN_BLOCK_SIZE)
- return;
- memset(pool, 0, arrsize);
- encode(poolsize - arrsize, true, (unsigned char *)pool + arrsize);
- }
- /* Walk through and try to coalesce */
- static bool try_coalesce(unsigned char *pool, unsigned long poolsize)
- {
- unsigned long len, prev_off = 0, prev_len = 0, off;
- bool free, prev_free = false, coalesced = false;
- off = free_array_size(poolsize);
- do {
- decode(&len, &free, pool + off);
- if (free && prev_free) {
- prev_len += len;
- encode(prev_len, true, pool + prev_off);
- coalesced = true;
- } else {
- prev_free = free;
- prev_off = off;
- prev_len = len;
- }
- off += len;
- } while (off < poolsize);
- /* Clear the free array. */
- if (coalesced)
- memset(pool, 0, free_array_size(poolsize));
- return coalesced;
- }
- static bool long_enough(unsigned long offset, unsigned long len,
- unsigned long size, unsigned long align)
- {
- unsigned long end = offset + len;
- offset += encode_len_with_header(len);
- offset = align_up(offset, align);
- return offset + size <= end;
- }
- static void add_to_free_array(unsigned char *arr,
- unsigned long poolsize,
- unsigned long size,
- unsigned long off)
- {
- unsigned long fa_off;
- if (size >= MAX_FREE_CACHED_SIZE)
- return;
- for (fa_off = free_array_off(size);
- fa_off + 3 < free_array_size(poolsize);
- fa_off += free_array_off(MAX_FREE_CACHED_SIZE)) {
- if (!arr[fa_off] && !arr[fa_off+1] && !arr[fa_off+2]) {
- arr[fa_off] = (off >> 16);
- arr[fa_off+1] = (off >> 8);
- arr[fa_off+2] = off;
- break;
- }
- }
- }
- void *tiny_alloc_get(void *pool, unsigned long poolsize,
- unsigned long size, unsigned long align)
- {
- unsigned long arrsize = free_array_size(poolsize);
- unsigned long len, off, actual, hdr, free_bucket;
- long fa_off;
- unsigned char *arr = pool;
- bool free, coalesced = false;
- /* We can't do anything with tiny pools. */
- if (poolsize < MIN_BLOCK_SIZE)
- return NULL;
- /* We don't do zero-allocs; allows 1 more offset in encoding. */
- if (!size)
- size = 1;
- /* Look for entries in free array, starting from right size up. */
- for (free_bucket = free_array_off(size);
- free_bucket < free_array_off(MAX_FREE_CACHED_SIZE);
- free_bucket += 3) {
- for (fa_off = free_bucket;
- fa_off + 3 < free_array_size(poolsize);
- fa_off += free_array_off(MAX_FREE_CACHED_SIZE)) {
- off = ((unsigned long)arr[fa_off]) << 16
- | ((unsigned long)arr[fa_off+1]) << 8
- | ((unsigned long)arr[fa_off+2]);
- if (!off)
- continue;
- decode(&len, &free, arr + off);
- if (long_enough(off, len, size, align)) {
- /* Remove it. */
- memset(&arr[fa_off], 0, 3);
- goto found;
- }
- }
- }
- again:
- off = arrsize;
- decode(&len, &free, arr + off);
- while (!free || !long_enough(off, len, size, align)) {
- /* Refill free array as we go. */
- if (free && coalesced)
- add_to_free_array(arr, poolsize, len, off);
- off += len;
- /* Hit end? */
- if (off == poolsize) {
- if (!coalesced && try_coalesce(pool, poolsize)) {
- coalesced = true;
- goto again;
- }
- return NULL;
- }
- decode(&len, &free, arr + off);
- }
- found:
- /* We have a free block. Since we walk from front, take far end. */
- actual = ((off + len - size) & ~(align - 1));
- hdr = actual - encode_len_with_header(off + len - actual);
- /* Do we have enough room to split? */
- if (hdr - off >= MIN_BLOCK_SIZE) {
- encode(hdr - off, true, arr + off);
- add_to_free_array(arr, poolsize, hdr - off, off);
- } else {
- hdr = off;
- }
- /* Make sure that we are all-zero up to actual, so we can walk back
- * and find header. */
- memset(arr + hdr, 0, actual - hdr);
- /* Create header for allocated block. */
- encode(off + len - hdr, false, arr + hdr);
- return arr + actual;
- }
- static unsigned char *to_hdr(void *p)
- {
- unsigned char *hdr = p;
- /* Walk back to find end of header. */
- while (!*(--hdr));
- assert(*hdr & TERM_BIT);
- /* Now walk back to find start of header. */
- if (!(*hdr & SINGLE_BYTE)) {
- while (!(*(--hdr) & TERM_BIT));
- }
- return hdr;
- }
- void tiny_alloc_free(void *pool, unsigned long poolsize, void *freep)
- {
- unsigned long len;
- unsigned char *arr = pool;
- unsigned char *hdr;
- bool free;
- /* Too small to do anything. */
- if (poolsize < MIN_BLOCK_SIZE)
- return;
- hdr = to_hdr(freep);
- decode(&len, &free, hdr);
- assert(!free);
- hdr[0] |= FREE_BIT;
- /* If an empty slot, put this in free array. */
- add_to_free_array(pool, poolsize, len, hdr - arr);
- }
- unsigned long tiny_alloc_size(void *pool, unsigned long poolsize, void *p)
- {
- unsigned char *hdr = to_hdr(p);
- unsigned long len, hdrlen;
- bool free;
- hdrlen = decode(&len, &free, hdr);
- return len - hdrlen;
- }
- /* Useful for gdb breakpoints. */
- static bool tiny_check_fail(void)
- {
- return false;
- }
- static bool check_decode(const unsigned char *arr, unsigned long len)
- {
- unsigned int i;
- if (len == 0)
- return tiny_check_fail();
- if (!(arr[0] & TERM_BIT))
- return tiny_check_fail();
- if (arr[0] & SINGLE_BYTE)
- return true;
- for (i = 1; i < len; i++) {
- if (arr[i] & TERM_BIT)
- return true;
- }
- return tiny_check_fail();
- }
- bool tiny_alloc_check(void *pool, unsigned long poolsize)
- {
- unsigned long arrsize = free_array_size(poolsize);
- unsigned char *arr = pool;
- unsigned long len, off, hdrlen;
- unsigned long i, freearr[arrsize], num_freearr = 0;
- bool free;
- if (poolsize < MIN_BLOCK_SIZE)
- return true;
- for (i = 0; i + 3 < free_array_size(poolsize); i += 3) {
- off = ((unsigned long)arr[i]) << 16
- | ((unsigned long)arr[i+1]) << 8
- | ((unsigned long)arr[i+2]);
- if (!off)
- continue;
- if (off >= poolsize)
- return tiny_check_fail();
- freearr[num_freearr++] = off;
- }
- for (off = arrsize; off < poolsize; off += len) {
- /* We should have a valid header. */
- if (!check_decode(arr + off, poolsize - off))
- return false;
- hdrlen = decode(&len, &free, arr + off);
- if (off + len > poolsize)
- return tiny_check_fail();
- if (hdrlen != encode_length(len - MIN_BLOCK_SIZE))
- return tiny_check_fail();
- for (i = 0; i < num_freearr; i++) {
- if (freearr[i] == off) {
- if (!free)
- return tiny_check_fail();
- memmove(&freearr[i], &freearr[i+1],
- (num_freearr-- - (i + 1))
- * sizeof(freearr[i]));
- break;
- }
- }
- }
- /* Now we should have found everything in freearr. */
- if (num_freearr)
- return tiny_check_fail();
- /* Now check that sizes are correct. */
- for (i = 0; i + 3 < free_array_size(poolsize); i += 3) {
- unsigned long fa_off;
- off = ((unsigned long)arr[i]) << 16
- | ((unsigned long)arr[i+1]) << 8
- | ((unsigned long)arr[i+2]);
- if (!off)
- continue;
- decode(&len, &free, arr + off);
- /* Would we expect to find this length in this bucket? */
- if (len >= MAX_FREE_CACHED_SIZE)
- return tiny_check_fail();
- for (fa_off = free_array_off(len);
- fa_off + 3 < free_array_size(poolsize);
- fa_off += free_array_off(MAX_FREE_CACHED_SIZE)) {
- if (fa_off == i)
- break;
- }
- if (fa_off != i)
- return tiny_check_fail();
- }
- return true;
- }
- /* FIXME: Implement. */
- void tiny_alloc_visualize(FILE *out, void *pool, unsigned long poolsize)
- {
- }
|