diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e52ac2..c148059 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,7 +80,6 @@ set( GL/state.c GL/texture.c GL/util.c - GL/yalloc/yalloc.c GL/alloc/alloc.c ${CMAKE_CURRENT_BINARY_DIR}/version.c ) diff --git a/GL/texture.c b/GL/texture.c index aebfde5..e699e3c 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -510,7 +510,7 @@ GLubyte _glInitTextures() { #ifdef __DREAMCAST__ /* Ensure memory is aligned */ - gl_assert((uintptr_t) YALLOC_BASE % 32 == 0); + gl_assert((uintptr_t) ALLOC_BASE % 32 == 0); #endif alloc_init(ALLOC_BASE, ALLOC_SIZE); diff --git a/GL/yalloc/LICENSE b/GL/yalloc/LICENSE deleted file mode 100644 index 8aa2645..0000000 --- a/GL/yalloc/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) [year] [fullname] - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/GL/yalloc/README.md b/GL/yalloc/README.md deleted file mode 100644 index ca23ec2..0000000 --- a/GL/yalloc/README.md +++ /dev/null @@ -1,158 +0,0 @@ -# Summary - -yalloc is a memory efficient allocator which is intended for embedded -applications that only have a low amount of RAM and want to maximize its -utilization. Properties of the allocator: - - - pools can be up to 128k - - user data is 32bit aligned - - 4 bytes overhead per allocation - - supports defragmentation - - uses a free list for first fit allocation strategy (most recently freed - blocks are used first) - - extensively tested (see section below) - - MIT license - -# Defragmentation - -This feature was the initial motivation for this implementation. Especially -when dealing with highly memory constrained environments fragmenting memory -pools can be annoying. For this reason this implementation supports -defragmentation which moves all allocated blocks into a contiguous range at the -beginning of the pool, leaving a maximized free range at the end. - -As there is no garbage collector or other runtime system involved that updates -the references, the application must do so. This is done in three steps: - - 1. yalloc_defrag_start() is called. This calculates the new - post-defragmentation-addresses for all allocations, but otherwise leaves - the allocations untouched. - - 2. yalloc_defrag_address() is called by the application for every pointer that - points to an allocation. It returns the post-defragmentation-address for - the allocation. The application must update all its relevant pointers this - way. Care must be taken not not yet dereference that moved pointers. If the - application works with hierarchical data then this can easily be done by - updating the pointers button up (first the leafs then their parents). - - 3. yalloc_defrag_commit() is called to finally perform the defragmentation. - All allocated blocks are moved to their post-defragmentation-address and - the application can continue using the pool the normal way. - -It is up to the application when (and if) it performs defragmentation. One -strategy would be to delay it until an allocation failure. Another approach -would be to perform the defragmentation regularly when there is nothing else to -do. - -# Configurable Defines - -INTERNAL_VALIDATE - -If this is not defined on the compiler commandline it will be defined as 0 if -NDEBUG is defined and otherwise as 1. If you want to disable internal -validation when NDEBUG is not defined then define INERNAL_VALIDATE as 0 on the -compiler commandline. - -If it is nonzero the heap will be validated via a bunch of assert() calls at -the end of every function that modifies the heap. This has roughly O(N*M) -overhead where N is the number of allocated blocks and M the number of free -blocks in a heap. For applications with enough live allocations this will get -significant. - -YALLOC_VALGRIND - -If this is defined in yalloc.c and NVALGRIND is not defined then -valgrind/memcheck.h is included and the the allocator functions tell valgrind -about the pool, the allocations and makes the block headers inaccessible outside -of yalloc-functions. This allows valgrind to detect a lot of the accidents that -can happen when dealing dynamic memory. This also adds some overhead for every -yalloc-call because most of them will "unprotect" the internal structure on -entry and "protect" it again (marking it as inaccessible for valgrind) before -returning. - -# Tests - -The tests rely on internal validation of the pool (see INTERNAL_VALIDATE) to -check that no assumptions about the internal structure of the pool are -violated. They additionally check for correctness of observations that can be -made by using the public functions of the allocator (like checking if user data -stays unmodified). There are a few different scripts that run tests: - - - run_coverage.sh runs a bunch of testfunctions that are carefully crafted to - cover all code paths. Coverage data is generated by clang and a summary is - shown at the end of the test. - - - run_valgrind.sh tests if the valgrind integration is working as expected, - runs the functions from the coverage test and some randomly generated - testcases under valgrind. - - - run_libfuzzer.sh uses libfuzzer from clang to generate interesting testcases - and runs them in multiple jobs in parallel for 10 seconds. It also generates - coverage data at the end (it always got 100% coverage in my testruns). - -All tests exit with 0 and print "All fine!" at the end if there where no -errors. Coverage deficits are not counted as error, so you have to look at the -summary (they should show 100% coverage!). - - -# Implementation Details - -The Headers and the user data are 32bit aligned. Headers have two 16bit fields -where the high 15 bits represent offsets (relative to the pools address) to the -previous/next block. The macros HDR_PTR() and HDR_OFFSET() are used to -translate an offset to an address and back. The 32bit alignment is exploited to -allow pools of up to 128k with that 15 significant bits. - -A pool is always occupied by non-overlapping blocks that link to their -previous/next block in address order via the prev/next field of Header. - -Free blocks are always joined: No two free blocks will ever be neighbors. - -Free blocks have an additional header of the same structure. This additional -header is used to build a list of free blocks (independent of their address -order). - -yalloc_free() will insert the freed block to the front of the free list. -yalloc_alloc() searches that list front to back and takes the first block that -is big enough to satisfy the allocation. - -There is always a Header at the front and at the end of the pool. The Header at -the end is degenerate: It is marked as "used" but has no next block (which is -usually used to determine the size of a block). - -The prev-field of the very first block in the pool has special meaning: It -points to the first free block in the pool. Or, if the pool is currently -defragmenting (after yalloc_defrag_start() and before yalloc_defrag_commit()), -points to the last header of the pool. This state can be recognized by checking -if it points to an empty block (normal pool state) or a used block -(defragmentation in progress). This logic can be seen in -yalloc_defrag_in_progress(). - -The lowest bit of next/prev have special meaning: - - - low bit of prev is set for free blocks - - - low bit of next is set for blocks with 32bit padding after the user data. - This is needed when a block is allocated from a free block that leaves only - 4 free bytes after the user data... which is not enough to insert a - free-header (which is needs 8 bytes). The padding will be reclaimed when - that block is freed or when the pool is defragmented. The predicate - isPadded() can be used to test if a block is padded. Free blocks are never - padded. - -The predicate isNil() can be used to test if an offset points nowhere (it tests -if all 15 high bits of an offset are 1). The constant NIL has all but the -lowest bit set. It is used to set offsets to point to nowhere, and in some -places it is used to mask out the actual address bits of an offset. This should -be kept in mind when modifying the code and updating prev/next: Think carefully -if you have to preserve the low bit when updating an offset! - -Defragmentation is done in two phases: First the user calls -yalloc_defrag_start(). This will put the pool in a special state where no -alloc/free-calls are allowed. In this state the prev-fields of the used blocks -have a special meaning: They store the offset that the block will have after -defragmentation finished. This information is used by yalloc_defrag_address() -which can be called by the application to query the new addresses for its -allocations. After the application has updated all its pointers it must call -yalloc_defrag_commit() which moves all used blocks in contiguous space at the -beginning of the pool, leaving one maximized free block at the end. diff --git a/GL/yalloc/yalloc.c b/GL/yalloc/yalloc.c deleted file mode 100644 index 6dcf0e5..0000000 --- a/GL/yalloc/yalloc.c +++ /dev/null @@ -1,803 +0,0 @@ -#include "yalloc.h" -#include "yalloc_internals.h" - -#include -#include - -#define ALIGN(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) - -#if defined(YALLOC_VALGRIND) && !defined(NVALGRIND) -# define USE_VALGRIND 1 -#else -# define USE_VALGRIND 0 -#endif - -#if USE_VALGRIND -# include -#else -# define VALGRIND_MAKE_MEM_UNDEFINED(p, s) ((void)0) -# define VALGRIND_MAKE_MEM_DEFINED(p, s) ((void)0) -# define VALGRIND_MAKE_MEM_NOACCESS(p, s) ((void)0) -# define VALGRIND_CREATE_MEMPOOL(pool, rz, z) ((void)0) -# define VALGRIND_MEMPOOL_ALLOC(pool, p, s) ((void)0) -# define VALGRIND_MEMPOOL_FREE(pool, p) ((void)0) -# define VALGRIND_MEMPOOL_CHANGE(pool, a, b, s) ((void)0) -#endif - -#define MARK_NEW_FREE_HDR(p) VALGRIND_MAKE_MEM_UNDEFINED(p, sizeof(Header) * 2) -#define MARK_NEW_HDR(p) VALGRIND_MAKE_MEM_UNDEFINED(p, sizeof(Header)) -#define PROTECT_HDR(p) VALGRIND_MAKE_MEM_NOACCESS(p, sizeof(Header)) -#define PROTECT_FREE_HDR(p) VALGRIND_MAKE_MEM_NOACCESS(p, sizeof(Header) * 2) -#define UNPROTECT_HDR(p) VALGRIND_MAKE_MEM_DEFINED(p, sizeof(Header)) -#define UNPROTECT_FREE_HDR(p) VALGRIND_MAKE_MEM_DEFINED(p, sizeof(Header) * 2) - - -#if USE_VALGRIND -static void _unprotect_pool(void * pool) -{ - Header * cur = (Header*)pool; - for (;;) - { - UNPROTECT_HDR(cur); - if (isFree(cur)) - UNPROTECT_HDR(cur + 1); - - if (isNil(cur->next)) - break; - - cur = HDR_PTR(cur->next); - } -} - -static void _protect_pool(void * pool) -{ - Header * cur = (Header*)pool; - while (cur) - { - Header * next = isNil(cur->next) ? NULL : HDR_PTR(cur->next); - - if (isFree(cur)) - VALGRIND_MAKE_MEM_NOACCESS(cur, (char*)next - (char*)cur); - else - PROTECT_HDR(cur); - - cur = next; - } -} -#define assert_is_pool(pool) assert(VALGRIND_MEMPOOL_EXISTS(pool)); - -#else - -static void _unprotect_pool(void * pool){(void)pool;} -static void _protect_pool(void * pool){(void)pool;} -#define assert_is_pool(pool) ((void)0) -#endif - -// internal version that does not unprotect/protect the pool -static int _yalloc_defrag_in_progress(void * pool) -{ - // fragmentation is indicated by a free list with one entry: the last block of the pool, which has its "free"-bit cleared. - Header * p = (Header*)pool; - if (isNil(p->prev)) - return 0; - - return !(HDR_PTR(p->prev)->prev & 1); -} - -int yalloc_defrag_in_progress(void * pool) -{ - _unprotect_pool(pool); - int ret = _yalloc_defrag_in_progress(pool); - _protect_pool(pool); - return ret; -} - -#if YALLOC_INTERNAL_VALIDATE - -static size_t _count_free_list_occurences(Header * pool, Header * blk) -{ - int n = 0; - if (!isNil(pool->prev)) - { - Header * cur = HDR_PTR(pool->prev); - for (;;) - { - if (cur == blk) - ++n; - - if (isNil(cur[1].next)) - break; - - cur = HDR_PTR(cur[1].next); - } - } - return n; -} - -static size_t _count_addr_list_occurences(Header * pool, Header * blk) -{ - size_t n = 0; - Header * cur = pool; - for (;;) - { - if (cur == blk) - ++n; - - if (isNil(cur->next)) - break; - - cur = HDR_PTR(cur->next); - } - return n; -} - -static void _validate_user_ptr(void * pool, void * p) -{ - Header * hdr = (Header*)p - 1; - size_t n = _count_addr_list_occurences((Header*)pool, hdr); - assert(n == 1 && !isFree(hdr)); -} - -/** -Validates if all the invariants of a pool are intact. - -This is very expensive when there are enough blocks in the heap (quadratic complexity!). -*/ -static void _yalloc_validate(void * pool_) -{ - Header * pool = (Header*)pool_; - Header * cur = pool; - - assert(!isNil(pool->next)); // there must always be at least two blocks: a free/used one and the final block at the end - - if (_yalloc_defrag_in_progress(pool)) - { - Header * prevUsed = NULL; - while (!isNil(cur->next)) - { - if (!isFree(cur)) - { // it is a used block - Header * newAddr = cur == pool ? pool : HDR_PTR(cur->prev); - assert(newAddr <= cur); - assert(newAddr >= pool); - - if (prevUsed) - { - Header * prevNewAddr = prevUsed == pool ? pool : HDR_PTR(prevUsed->prev); - size_t prevBruttoSize = (char*)HDR_PTR(prevUsed->next) - (char*)prevUsed; - if (isPadded(prevUsed)) - prevBruttoSize -= 4; // remove padding - assert((char*)newAddr == (char*)prevNewAddr + prevBruttoSize); - } - else - { - assert(newAddr == pool); - } - - prevUsed = cur; - } - - cur = HDR_PTR(cur->next); - } - - assert(cur == HDR_PTR(pool->prev)); // the free-list should point to the last block - assert(!isFree(cur)); // the last block must not be free - } - else - { - Header * prev = NULL; - - // iterate blocks in address order - for (;;) - { - if (prev) - { - Header * x = HDR_PTR(cur->prev); - assert(x == prev); - } - - int n = _count_free_list_occurences(pool, cur); - if (isFree(cur)) - { // it is a free block - assert(n == 1); - assert(!isPadded(cur)); // free blocks must have a zero padding-bit - - if (prev) - { - assert(!isFree(prev)); // free blocks must not be direct neighbours - } - } - else - { - assert(n == 0); - } - - if (isNil(cur->next)) - break; - - Header * next = HDR_PTR(cur->next); - assert((char*)next >= (char*)cur + sizeof(Header) * 2); - prev = cur; - cur = next; - } - - assert(isNil(cur->next)); - - if (!isNil(pool->prev)) - { - // iterate free-list - Header * f = HDR_PTR(pool->prev); - assert(isNil(f[1].prev)); - for (;;) - { - assert(isFree(f)); // must be free - - int n = _count_addr_list_occurences(pool, f); - assert(n == 1); - - if (isNil(f[1].next)) - break; - - f = HDR_PTR(f[1].next); - } - } - } -} - -#else -static void _yalloc_validate(void * pool){(void)pool;} -static void _validate_user_ptr(void * pool, void * p){(void)pool; (void)p;} -#endif - -int yalloc_init(void * pool, size_t size) -{ - if (size > MAX_POOL_SIZE) - return -1; - - // TODO: Error when pool is not properly aligned - - // TODO: Error when size is not a multiple of the alignment? - while (size % sizeof(Header)) - --size; - - if(size < sizeof(Header) * 3) - return -1; - - VALGRIND_CREATE_MEMPOOL(pool, 0, 0); - - Header * first = (Header*)pool; - Header * last = (Header*)((char*)pool + size) - 1; - - MARK_NEW_FREE_HDR(first); - MARK_NEW_HDR(first); - - first->prev = HDR_OFFSET(first) | 1; - first->next = HDR_OFFSET(last); - first[1].prev = NIL; - first[1].next = NIL; - - last->prev = HDR_OFFSET(first); - last->next = NIL; - - _unprotect_pool(pool); - _yalloc_validate(pool); - _protect_pool(pool); - return 0; -} - -void yalloc_deinit(void * pool) -{ -#if USE_VALGRIND - VALGRIND_DESTROY_MEMPOOL(pool); - - Header * last = (Header*)pool; - UNPROTECT_HDR(last); - while (!isNil(last->next)) - { - Header * next = HDR_PTR(last->next); - UNPROTECT_HDR(next); - last = next; - } - - VALGRIND_MAKE_MEM_UNDEFINED(pool, (char*)(last + 1) - (char*)pool); -#else - (void)pool; -#endif -} - - -void * yalloc_alloc(void * pool, size_t size) -{ - assert_is_pool(pool); - _unprotect_pool(pool); - assert(!_yalloc_defrag_in_progress(pool)); - _yalloc_validate(pool); - if (!size) - { - _protect_pool(pool); - return NULL; - } - - Header * root = (Header*)pool; - if (isNil(root->prev)) - { - _protect_pool(pool); - return NULL; /* no free block, no chance to allocate anything */ // TODO: Just read up which C standard supports single line comments and then fucking use them! - } - - /* round up to alignment */ - size = ALIGN(size, 32); - - size_t bruttoSize = size + sizeof(Header); - Header * prev = NULL; - Header * cur = HDR_PTR(root->prev); - for (;;) - { - size_t curSize = (char*)HDR_PTR(cur->next) - (char*)cur; /* size of the block, including its header */ - - if (curSize >= bruttoSize) // it is big enough - { - // take action for unused space in the free block - if (curSize >= bruttoSize + sizeof(Header) * 2) - { // the leftover space is big enough to make it a free block - // Build a free block from the unused space and insert it into the list of free blocks after the current free block - Header * tail = (Header*)((char*)cur + bruttoSize); - MARK_NEW_FREE_HDR(tail); - - // update address-order-list - tail->next = cur->next; - tail->prev = HDR_OFFSET(cur) | 1; - HDR_PTR(cur->next)->prev = HDR_OFFSET(tail); // NOTE: We know the next block is used because free blocks are never neighbours. So we don't have to care about the lower bit which would be set for the prev of a free block. - cur->next = HDR_OFFSET(tail); - - // update list of free blocks - tail[1].next = cur[1].next; - // NOTE: tail[1].prev is updated in the common path below (assignment to "HDR_PTR(cur[1].next)[1].prev") - - if (!isNil(cur[1].next)) - HDR_PTR(cur[1].next)[1].prev = HDR_OFFSET(tail); - cur[1].next = HDR_OFFSET(tail); - } - else if (curSize > bruttoSize) - { // there will be unused space, but not enough to insert a free header - internal_assert(curSize - bruttoSize == sizeof(Header)); // unused space must be enough to build a free-block or it should be exactly the size of a Header - cur->next |= 1; // set marker for "has unused trailing space" - } - else - { - internal_assert(curSize == bruttoSize); - } - - cur->prev &= NIL; // clear marker for "is a free block" - - // remove from linked list of free blocks - if (prev) - prev[1].next = cur[1].next; - else - { - uint32_t freeBit = isFree(root); - root->prev = (cur[1].next & NIL) | freeBit; - } - - if (!isNil(cur[1].next)) - HDR_PTR(cur[1].next)[1].prev = prev ? HDR_OFFSET(prev) : NIL; - - _yalloc_validate(pool); - VALGRIND_MEMPOOL_ALLOC(pool, cur + 1, size); - _protect_pool(pool); - return cur + 1; // return address after the header - } - - if (isNil(cur[1].next)) - break; - - prev = cur; - cur = HDR_PTR(cur[1].next); - } - - _yalloc_validate(pool); - _protect_pool(pool); - return NULL; -} - -// Removes a block from the free-list and moves the pools first-free-bock pointer to its successor if it pointed to that block. -static void unlink_from_free_list(Header * pool, Header * blk) -{ - // update the pools pointer to the first block in the free list if necessary - if (isNil(blk[1].prev)) - { // the block is the first in the free-list - // make the pools first-free-pointer point to the next in the free list - uint32_t freeBit = isFree(pool); - pool->prev = (blk[1].next & NIL) | freeBit; - } - else - HDR_PTR(blk[1].prev)[1].next = blk[1].next; - - if (!isNil(blk[1].next)) - HDR_PTR(blk[1].next)[1].prev = blk[1].prev; -} - -size_t yalloc_block_size(void * pool, void * p) -{ - Header * a = (Header*)p - 1; - UNPROTECT_HDR(a); - Header * b = HDR_PTR(a->next); - size_t payloadSize = (char*)b - (char*)p; - if (isPadded(a)) - payloadSize -= sizeof(Header); - PROTECT_HDR(a); - return payloadSize; -} - -void yalloc_free(void * pool_, void * p) -{ - assert_is_pool(pool_); - assert(!yalloc_defrag_in_progress(pool_)); - if (!p) - return; - - _unprotect_pool(pool_); - - Header * pool = (Header*)pool_; - Header * cur = (Header*)p - 1; - - // get pointers to previous/next block in address order - Header * prev = cur == pool || isNil(cur->prev) ? NULL : HDR_PTR(cur->prev); - Header * next = isNil(cur->next) ? NULL : HDR_PTR(cur->next); - - int prevFree = prev && isFree(prev); - int nextFree = next && isFree(next); - -#if USE_VALGRIND - { - unsigned errs = VALGRIND_COUNT_ERRORS; - VALGRIND_MEMPOOL_FREE(pool, p); - if (VALGRIND_COUNT_ERRORS > errs) - { // early exit if the free was invalid (so we get a valgrind error and don't mess up the pool, which is helpful for testing if invalid frees are detected by valgrind) - _protect_pool(pool_); - return; - } - } -#endif - - _validate_user_ptr(pool_, p); - - if (prevFree && nextFree) - { // the freed block has two free neighbors - unlink_from_free_list(pool, prev); - unlink_from_free_list(pool, next); - - // join prev, cur and next - prev->next = next->next; - HDR_PTR(next->next)->prev = cur->prev; - - // prev is now the block we want to push onto the free-list - cur = prev; - } - else if (prevFree) - { - unlink_from_free_list(pool, prev); - - // join prev and cur - prev->next = cur->next; - HDR_PTR(cur->next)->prev = cur->prev; - - // prev is now the block we want to push onto the free-list - cur = prev; - } - else if (nextFree) - { - unlink_from_free_list(pool, next); - - // join cur and next - cur->next = next->next; - HDR_PTR(next->next)->prev = next->prev & NIL; - } - - // if there is a previous block and that block has padding then we want to grow the new free block into that padding - if (cur != pool && !isNil(cur->prev)) - { // there is a previous block - Header * left = HDR_PTR(cur->prev); - if (isPadded(left)) - { // the previous block has padding, so extend the current block to consume move the padding to the current free block - Header * grown = cur - 1; - MARK_NEW_HDR(grown); - grown->next = cur->next; - grown->prev = cur->prev; - left->next = HDR_OFFSET(grown); - if (!isNil(cur->next)) - HDR_PTR(cur->next)->prev = HDR_OFFSET(grown); - - cur = grown; - } - } - - cur->prev |= 1; // it becomes a free block - cur->next &= NIL; // reset padding-bit - UNPROTECT_HDR(cur + 1); - cur[1].prev = NIL; // it will be the first free block in the free list, so it has no prevFree - - if (!isNil(pool->prev)) - { // the free-list was already non-empty - HDR_PTR(pool->prev)[1].prev = HDR_OFFSET(cur); // make the first entry in the free list point back to the new free block (it will become the first one) - cur[1].next = pool->prev; // the next free block is the first of the old free-list - } - else - cur[1].next = NIL; // free-list was empty, so there is no successor - - VALGRIND_MAKE_MEM_NOACCESS(cur + 2, (char*)HDR_PTR(cur->next) - (char*)(cur + 2)); - - // now the freed block is the first in the free-list - - // update the offset to the first element of the free list - uint32_t freeBit = isFree(pool); // remember the free-bit of the offset - pool->prev = HDR_OFFSET(cur) | freeBit; // update the offset and restore the free-bit - _yalloc_validate(pool); - _protect_pool(pool); -} - -size_t yalloc_count_free(void * pool_) -{ - assert_is_pool(pool_); - _unprotect_pool(pool_); - assert(!_yalloc_defrag_in_progress(pool_)); - Header * pool = (Header*)pool_; - size_t bruttoFree = 0; - Header * cur = pool; - - _yalloc_validate(pool); - - for (;;) - { - if (isFree(cur)) - { // it is a free block - bruttoFree += (char*)HDR_PTR(cur->next) - (char*)cur; - } - else - { // it is a used block - if (isPadded(cur)) - { // the used block is padded - bruttoFree += sizeof(Header); - } - } - - if (isNil(cur->next)) - break; - - cur = HDR_PTR(cur->next); - } - - _protect_pool(pool); - - if (bruttoFree < sizeof(Header)) - { - internal_assert(!bruttoFree); // free space should always be a multiple of sizeof(Header) - return 0; - } - - return bruttoFree - sizeof(Header); -} - -size_t yalloc_count_continuous(void * pool_) -{ - assert_is_pool(pool_); - _unprotect_pool(pool_); - assert(!_yalloc_defrag_in_progress(pool_)); - Header * pool = (Header*)pool_; - size_t largestFree = 0; - Header * cur = pool; - - _yalloc_validate(pool); - - for (;;) - { - if (isFree(cur)) - { // it is a free block - size_t temp = (uintptr_t)HDR_PTR(cur->next) - (uintptr_t)cur; - if(temp > largestFree) - largestFree = temp; - } - - if (isNil(cur->next)) - break; - - cur = HDR_PTR(cur->next); - } - - _protect_pool(pool); - - if (largestFree < sizeof(Header)) - { - internal_assert(!largestFree); // free space should always be a multiple of sizeof(Header) - return 0; - } - - return largestFree - sizeof(Header); -} - -void * yalloc_first_used(void * pool) -{ - assert_is_pool(pool); - _unprotect_pool(pool); - Header * blk = (Header*)pool; - while (!isNil(blk->next)) - { - if (!isFree(blk)) - { - _protect_pool(pool); - return blk + 1; - } - - blk = HDR_PTR(blk->next); - } - - _protect_pool(pool); - return NULL; -} - -void * yalloc_next_used(void * pool, void * p) -{ - assert_is_pool(pool); - _unprotect_pool(pool); - _validate_user_ptr(pool, p); - Header * prev = (Header*)p - 1; - assert(!isNil(prev->next)); // the last block should never end up as input to this function (because it is not user-visible) - - Header * blk = HDR_PTR(prev->next); - while (!isNil(blk->next)) - { - if (!isFree(blk)) - { - _protect_pool(pool); - return blk + 1; - } - - blk = HDR_PTR(blk->next); - } - - _protect_pool(pool); - return NULL; -} - -void yalloc_defrag_start(void * pool_) -{ - assert_is_pool(pool_); - _unprotect_pool(pool_); - assert(!_yalloc_defrag_in_progress(pool_)); - Header * pool = (Header*)pool_; - - // iterate over all blocks in address order and store the post-defragment address of used blocks in their "prev" field - size_t end = 0; // offset for the next used block - Header * blk = (Header*)pool; - for (; !isNil(blk->next); blk = HDR_PTR(blk->next)) - { - if (!isFree(blk)) - { // it is a used block - blk->prev = end >> 1; - internal_assert((char*)HDR_PTR(blk->prev) == (char*)pool + end); - - size_t bruttoSize = (char*)HDR_PTR(blk->next) - (char*)blk; - - if (isPadded(blk)) - { // the block is padded - bruttoSize -= sizeof(Header); - } - - end += bruttoSize; - internal_assert(end % sizeof(Header) == 0); - } - } - - // blk is now the last block (the dummy "used" block at the end of the pool) - internal_assert(isNil(blk->next)); - internal_assert(!isFree(blk)); - - // mark the pool as "defragementation in progress" - uint32_t freeBit = isFree(pool); - pool->prev = (HDR_OFFSET(blk) & NIL) | freeBit; - - _yalloc_validate(pool); - internal_assert(yalloc_defrag_in_progress(pool)); - _protect_pool(pool); -} - -void * yalloc_defrag_address(void * pool_, void * p) -{ - assert_is_pool(pool_); - assert(yalloc_defrag_in_progress(pool_)); - if (!p) - return NULL; - - Header * pool = (Header*)pool_; - - _unprotect_pool(pool); - _validate_user_ptr(pool_, p); - - if (pool + 1 == p) - return pool + 1; // "prev" of the first block points to the last used block to mark the pool as "defragmentation in progress" - - Header * blk = (Header*)p - 1; - - void * defragP = HDR_PTR(blk->prev) + 1; - - _protect_pool(pool); - return defragP; -} - -void yalloc_defrag_commit(void * pool_) -{ - assert_is_pool(pool_); - _unprotect_pool(pool_); - assert(_yalloc_defrag_in_progress(pool_)); - Header * pool = (Header*)pool_; - - // iterate over all blocks in address order and move them - size_t end = 0; // offset for the next used block - Header * blk = pool; - Header * lastUsed = NULL; - while (!isNil(blk->next)) - { - if (!isFree(blk)) - { // it is a used block - size_t bruttoSize = (char*)HDR_PTR(blk->next) - (char*)blk; - - if (isPadded(blk)) - { // the block is padded - bruttoSize -= sizeof(Header); - } - - Header * next = HDR_PTR(blk->next); - - blk->prev = lastUsed ? HDR_OFFSET(lastUsed) : NIL; - blk->next = (end + bruttoSize) >> 1; - - lastUsed = (Header*)((char*)pool + end); - VALGRIND_MAKE_MEM_UNDEFINED(lastUsed, (char*)blk - (char*)lastUsed); - memmove(lastUsed, blk, bruttoSize); - VALGRIND_MEMPOOL_CHANGE(pool, blk + 1, lastUsed + 1, bruttoSize - sizeof(Header)); - - end += bruttoSize; - blk = next; - } - else - blk = HDR_PTR(blk->next); - } - - // blk is now the last block (the dummy "used" block at the end of the pool) - internal_assert(isNil(blk->next)); - internal_assert(!isFree(blk)); - - if (lastUsed) - { - Header * gap = HDR_PTR(lastUsed->next); - if (gap == blk) - { // there is no gap - pool->prev = NIL; // the free list is empty - blk->prev = HDR_OFFSET(lastUsed); - } - else if (blk - gap > 1) - { // the gap is big enouogh for a free Header - - // set a free list that contains the gap as only element - gap->prev = HDR_OFFSET(lastUsed) | 1; - gap->next = HDR_OFFSET(blk); - gap[1].prev = NIL; - gap[1].next = NIL; - pool->prev = blk->prev = HDR_OFFSET(gap); - } - else - { // there is a gap, but it is too small to be used as free-list-node, so just make it padding of the last used block - lastUsed->next = HDR_OFFSET(blk) | 1; - pool->prev = NIL; - blk->prev = HDR_OFFSET(lastUsed); - } - } - else - { // the pool is empty - pool->prev = 1; - } - - internal_assert(!_yalloc_defrag_in_progress(pool)); - _yalloc_validate(pool); - _protect_pool(pool); -} diff --git a/GL/yalloc/yalloc.h b/GL/yalloc/yalloc.h deleted file mode 100644 index 4349eb9..0000000 --- a/GL/yalloc/yalloc.h +++ /dev/null @@ -1,176 +0,0 @@ -/** -@file - -API of the yalloc allocator. -*/ - -#ifndef YALLOC_H -#define YALLOC_H - -#include - -/** -Maximum supported pool size. yalloc_init() will fail for larger pools. -*/ -#define MAX_POOL_SIZE ((2 << 24) - 4) - -/** -Creates a pool inside a given buffer. - -Pools must be deinitialized with yalloc_deinit() when they are no longer needed. - -@param pool The starting address of the pool. It must have at least 16bit -alignment (internal structure uses 16bit integers). Allocations are placed at -32bit boundaries starting from this address, so if the user data should be -32bit aligned then this address has to be 32bit aligned. Typically an address -of static memory, or an array on the stack is used if the pool is only used -temporarily. -@param size Size of the pool. -@return 0 on success, nonzero if the size is not supported. - */ -int yalloc_init(void * pool, size_t size); - -/** -Deinitializes the buffer that is used by the pool and makes it available for other use. - -The content of the buffer is undefined after this. - -@param pool The starting address of an initialized pool. -*/ -void yalloc_deinit(void * pool); - -/** -Allocates a block of memory from a pool. - -This function mimics malloc(). - -The pool must not be in the "defragmenting" state when this function is called. - -@param pool The starting address of an initialized pool. -@param size Number of bytes to allocate. -@return Allocated buffer or \c NULL if there was no free range that could serve -the allocation. See @ref yalloc_defrag_start() for a way to remove -fragmentation which may cause allocations to fail even when there is enough -space in total. -*/ -void * yalloc_alloc(void * pool, size_t size); - -/** -Returns an allocation to a pool. - -This function mimics free(). - -The pool must not be in the "defragmenting" state when this function is called. - -@param pool The starting address of the initialized pool the allocation comes from. -@param p An address that was returned from yalloc_alloc() of the same pool. -*/ -void yalloc_free(void * pool, void * p); - -/** -Returns the maximum size of a successful allocation (assuming a completely unfragmented heap). - -After defragmentation the first allocation with the returned size is guaranteed to succeed. - -@param pool The starting address of an initialized pool. -@return Number of bytes that can be allocated (assuming the pool is defragmented). -*/ -size_t yalloc_count_free(void * pool); - -/** -Returns the maximum continuous free area. - -@param pool The starting address of an initialized pool. -@return Number of free bytes that exist continuously. -*/ -size_t yalloc_count_continuous(void * pool_); - -/** -Queries the usable size of an allocated block. - -@param pool The starting address of the initialized pool the allocation comes from. -@param p An address that was returned from yalloc_alloc() of the same pool. -@return Size of the memory block. This is the size passed to @ref yalloc_alloc() rounded up to 4. -*/ -size_t yalloc_block_size(void * pool, void * p); - -/** -Finds the first (in address order) allocation of a pool. - -@param pool The starting address of an initialized pool. -@return Address of the allocation the lowest address inside the pool (this is -what @ref yalloc_alloc() returned), or \c NULL if there is no used block. -*/ -void * yalloc_first_used(void * pool); - -/** -Given a pointer to an allocation finds the next (in address order) used block of a pool. - -@param pool The starting address of the initialized pool the allocation comes from. -@param p Pointer to an allocation in that pool, typically comes from a previous -call to @ref yalloc_first_used() -*/ -void * yalloc_next_used(void * pool, void * p); - -/** -Starts defragmentation for a pool. - -Allocations will stay where they are. But the pool is put in the "defagmenting" -state (see @ref yalloc_defrag_in_progress()). - -The pool must not be in the "defragmenting" state when this function is called. -The pool is put into the "defragmenting" state by this function. - -@param pool The starting address of an initialized pool. -*/ -void yalloc_defrag_start(void * pool); - -/** -Returns the address that an allocation will have after @ref yalloc_defrag_commit() is called. - -The pool must be in the "defragmenting" state when this function is called. - -@param pool The starting address of the initialized pool the allocation comes from. -@param p Pointer to an allocation in that pool. -@return The address the alloation will have after @ref yalloc_defrag_commit() is called. -*/ -void * yalloc_defrag_address(void * pool, void * p); - -/** -Finishes the defragmentation. - -The content of all allocations in the pool will be moved to the address that -was reported by @ref yalloc_defrag_address(). The pool will then have only one -free block. This means that an yalloc_alloc(pool, yalloc_count_free(pool)) -will succeed. - -The pool must be in the "defragmenting" state when this function is called. The -pool is put back to normal state by this function. - -@param pool The starting address of an initialized pool. -*/ -void yalloc_defrag_commit(void * pool); - -/** -Tells if the pool is in the "defragmenting" state (after a @ref yalloc_defrag_start() and before a @ref yalloc_defrag_commit()). - -@param pool The starting address of an initialized pool. -@return Nonzero if the pool is currently in the "defragmenting" state. -*/ -int yalloc_defrag_in_progress(void * pool); - - -/** -Helper function that dumps the state of the pool to stdout. - -This function is only available if build with yalloc_dump.c. This -function only exists for debugging purposes and can be ignored by normal users -that are not interested in the internal structure of the implementation. - -@param pool The starting address of an initialized pool. -@param name A string that is used as "Title" for the output. -*/ -void yalloc_dump(void * pool, char * name); - - -#endif // YALLOC_H diff --git a/GL/yalloc/yalloc_dump.c b/GL/yalloc/yalloc_dump.c deleted file mode 100644 index f0dfdcb..0000000 --- a/GL/yalloc/yalloc_dump.c +++ /dev/null @@ -1,39 +0,0 @@ -#include "yalloc_internals.h" - -#include - -static void printOffset(void * pool, char * name, uint16_t offset) -{ - if (isNil(offset)) - printf(" %s: nil\n", name); - else - printf(" %s: %td\n", name, (char*)HDR_PTR(offset) - (char*)pool); -} - -void yalloc_dump(void * pool, char * name) -{ - printf("---- %s ----\n", name); - Header * cur = (Header*)pool; - for (;;) - { - printf(isFree(cur) ? "%td: free @%p\n" : "%td: used @%p\n", (char*)cur - (char*)pool, cur); - printOffset(pool, cur == pool ? "first free" : "prev", cur->prev); - printOffset(pool, "next", cur->next); - if (isFree(cur)) - { - printOffset(pool, "prevFree", cur[1].prev); - printOffset(pool, "nextFree", cur[1].next); - } - else - printf(" payload includes padding: %i\n", isPadded(cur)); - - if (isNil(cur->next)) - break; - - printf(" %td bytes payload\n", (char*)HDR_PTR(cur->next) - (char*)cur - sizeof(Header)); - - cur = HDR_PTR(cur->next); - } - - fflush(stdout); -} diff --git a/GL/yalloc/yalloc_internals.h b/GL/yalloc/yalloc_internals.h deleted file mode 100644 index ffb70cb..0000000 --- a/GL/yalloc/yalloc_internals.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef YALLOC_INTERNALS_H -#define YALLOC_INTERNALS_H - -#include - -typedef struct -{ - uint32_t prev; // low bit set if free - uint32_t next; // for used blocks: low bit set if unused header at the end - - /* We need user data to be 32-byte aligned, so the header needs - * to be 32 bytes in size (as user data follows the header) */ - uint8_t padding[32 - (sizeof(uint32_t) * 2)]; -} Header; - -// NOTE: We have 32bit aligned data and 16bit offsets where the lowest bit is used as flag. So we remove the low bit and shift by 1 to address 128k bytes with the 15bit significant offset bits. - -#define NIL 0xFFFFFFFEu - -// return Header-address for a prev/next -#define HDR_PTR(offset) ((Header*)((char*)pool + (((offset) & NIL)<<1))) - -// return a prev/next for a Header-address -#define HDR_OFFSET(blockPtr) ((uint32_t)(((char*)blockPtr - (char*)pool) >> 1)) - -#ifndef YALLOC_INTERNAL_VALIDATE -# ifdef NDEBUG -# define YALLOC_INTERNAL_VALIDATE 0 -# else -# define YALLOC_INTERNAL_VALIDATE 1 -#endif -#endif - - -/* -internal_assert() is used in some places to check internal expections. -Activate this if you modify the code to detect problems as early as possible. -In other cases this should be deactivated. -*/ -#if 0 -#define internal_assert assert -#else -#define internal_assert(condition)((void) 0) -#endif - -// detects offsets that point nowhere -static inline int isNil(uint32_t offset) -{ - return (offset | 1) == 0xFFFFFFFF; -} - -static inline int isFree(Header * hdr) -{ - return hdr->prev & 1; -} - -static inline int isPadded(Header * hdr) -{ - return hdr->next & 1; -} - - -#endif // YALLOC_INTERNALS_H