mirror of
https://github.com/w23/xash3d-fwgs
synced 2024-12-16 22:20:01 +01:00
437 lines
11 KiB
C
437 lines
11 KiB
C
#include "alolcator.h"
|
|
#include <stdlib.h> // malloc/free
|
|
#include <string.h> // memcpy
|
|
|
|
//#include "vk_common.h"
|
|
|
|
#define MALLOC malloc
|
|
#define FREE free
|
|
|
|
#ifndef ASSERT
|
|
#include <assert.h>
|
|
#define ASSERT(...) assert(__VA_ARGS__)
|
|
#endif
|
|
|
|
#ifndef ALIGN_UP
|
|
#define ALIGN_UP(ptr, align) ((((ptr) + (align) - 1) / (align)) * (align))
|
|
#endif
|
|
|
|
typedef struct {
|
|
int item_size;
|
|
int capacity;
|
|
int free;
|
|
char *pool;
|
|
int *free_list;
|
|
} pool_t;
|
|
|
|
static void poolGrow(pool_t *pool, int new_capacity) {
|
|
const size_t new_pool_size = pool->item_size * new_capacity;
|
|
int *const new_free_list = MALLOC(new_pool_size + sizeof(int) * new_capacity);
|
|
char *const new_pool = (char*)(new_free_list + new_capacity);
|
|
const int new_items = new_capacity - pool->capacity;
|
|
|
|
for (int i = 0; i < pool->free; ++i)
|
|
new_free_list[i] = pool->free_list[i];
|
|
|
|
for (int i = 0; i < new_items; ++i)
|
|
new_free_list[pool->free + i] = new_capacity - i - 1;
|
|
|
|
if (pool->capacity)
|
|
memcpy(new_pool, pool->pool, pool->item_size * pool->capacity);
|
|
|
|
if (pool->free_list)
|
|
FREE(pool->free_list);
|
|
|
|
pool->free_list = new_free_list;
|
|
pool->pool = new_pool;
|
|
pool->free += new_items;
|
|
pool->capacity = new_capacity;
|
|
}
|
|
|
|
static pool_t poolCreate(int item_size, int capacity) {
|
|
pool_t pool = {0};
|
|
pool.item_size = item_size;
|
|
poolGrow(&pool, capacity);
|
|
return pool;
|
|
}
|
|
|
|
static void poolDestroy(pool_t* pool) {
|
|
FREE(pool->free_list);
|
|
pool->capacity = 0;
|
|
}
|
|
|
|
// invalidates all poolGet pointers returned prior to this function
|
|
static int poolAlloc(pool_t* pool) {
|
|
if (pool->free == 0) {
|
|
const int new_capacity = pool->capacity * 3 / 2;
|
|
poolGrow(pool, new_capacity);
|
|
}
|
|
|
|
pool->free--;
|
|
return pool->free_list[pool->free];
|
|
}
|
|
|
|
inline static void* poolGet(pool_t* pool, int item) {
|
|
ASSERT(item >= 0);
|
|
ASSERT(item < pool->capacity);
|
|
return pool->pool + pool->item_size * item;
|
|
}
|
|
|
|
static void poolFree(pool_t* pool, int item) {
|
|
ASSERT(item >= 0);
|
|
ASSERT(item < pool->capacity);
|
|
ASSERT(pool->free < pool->capacity);
|
|
|
|
pool->free_list[pool->free++] = item;
|
|
}
|
|
|
|
enum {
|
|
BlockFlag_Empty = 0,
|
|
BlockFlag_Allocated = 1,
|
|
};
|
|
|
|
typedef struct {
|
|
int next, prev;
|
|
int flags;
|
|
|
|
alo_size_t begin;
|
|
alo_size_t end;
|
|
} block_t;
|
|
|
|
typedef struct alo_pool_s {
|
|
pool_t blocks;
|
|
|
|
alo_size_t min_alignment;
|
|
alo_size_t size;
|
|
int first_block;
|
|
|
|
// TODO optimize: first_free block, and chain of free blocks
|
|
} alo_pool_t;
|
|
|
|
#define DEFAULT_CAPACITY 256
|
|
|
|
struct alo_pool_s* aloPoolCreate(alo_size_t size, int expected_allocations, alo_size_t min_alignment) {
|
|
alo_pool_t *pool = MALLOC(sizeof(*pool));
|
|
block_t *b;
|
|
pool->min_alignment = min_alignment;
|
|
pool->size = size;
|
|
pool->blocks = poolCreate(sizeof(block_t), expected_allocations > 0 ? expected_allocations : DEFAULT_CAPACITY);
|
|
pool->first_block = poolAlloc(&pool->blocks);
|
|
b = poolGet(&pool->blocks, pool->first_block);
|
|
b->flags = BlockFlag_Empty;
|
|
b->next = b->prev = -1;
|
|
b->begin = 0;
|
|
b->end = size;
|
|
return pool;
|
|
}
|
|
|
|
void aloPoolDestroy(struct alo_pool_s *pool) {
|
|
poolDestroy(&pool->blocks);
|
|
FREE(pool);
|
|
}
|
|
|
|
static int splitBlockAt(pool_t *blocks, int index, alo_size_t at) {
|
|
block_t *block = poolGet(blocks, index);
|
|
ASSERT(block->begin < at);
|
|
ASSERT(block->end > at);
|
|
|
|
const int new_index = poolAlloc(blocks);
|
|
block_t *const new_block = poolGet(blocks, new_index);
|
|
|
|
// poolAlloc may reallocate, retrieve pointer again
|
|
block = poolGet(blocks, index);
|
|
|
|
if (block->next >= 0) {
|
|
block_t *const next = poolGet(blocks, block->next);
|
|
ASSERT(next->prev == index);
|
|
next->prev = new_index;
|
|
}
|
|
|
|
new_block->next = block->next;
|
|
new_block->prev = index;
|
|
new_block->flags = block->flags;
|
|
new_block->end = block->end;
|
|
new_block->begin = at;
|
|
|
|
block->next = new_index;
|
|
block->end = at;
|
|
return new_index;
|
|
}
|
|
|
|
alo_block_t aloPoolAllocate(struct alo_pool_s* pool, alo_size_t size, alo_size_t alignment) {
|
|
alo_block_t ret = {0};
|
|
block_t *b;
|
|
alignment = alignment > pool->min_alignment ? alignment : pool->min_alignment;
|
|
for (int i = pool->first_block; i >= 0; i = b->next) {
|
|
b = poolGet(&pool->blocks, i);
|
|
if (b->flags != BlockFlag_Empty)
|
|
continue;
|
|
|
|
{
|
|
const alo_size_t offset = ALIGN_UP(b->begin, alignment);
|
|
const alo_size_t end = offset + size;
|
|
const alo_size_t alignment_hole = offset - b->begin;
|
|
|
|
if (end > b->end)
|
|
continue;
|
|
|
|
// TODO min allocation size?
|
|
if (alignment_hole > 0) {
|
|
// old block remains the alignment_hole
|
|
// new block is where we'll allocate
|
|
i = splitBlockAt(&pool->blocks, i, offset);
|
|
b = poolGet(&pool->blocks, i);
|
|
}
|
|
|
|
if (end != b->end) {
|
|
// new block is after the one we'll allocate on
|
|
// so we don't care about it
|
|
splitBlockAt(&pool->blocks, i, end);
|
|
|
|
// splitting may have incurred realloc, retrieve the pointer again
|
|
b = poolGet(&pool->blocks, i);
|
|
}
|
|
|
|
b->flags = BlockFlag_Allocated;
|
|
|
|
ret.index = i;
|
|
ret.offset = offset;
|
|
ret.size = size;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void aloPoolFree(struct alo_pool_s *pool, int index) {
|
|
block_t *iblock = poolGet(&pool->blocks, index);
|
|
ASSERT((iblock->flags & BlockFlag_Allocated) != 0);
|
|
|
|
iblock->flags = BlockFlag_Empty;
|
|
|
|
{
|
|
block_t *const prev = (iblock->prev >= 0) ? poolGet(&pool->blocks, iblock->prev) : NULL;
|
|
block_t *const next = (iblock->next >= 0) ? poolGet(&pool->blocks, iblock->next) : NULL;
|
|
|
|
// join with previous block if empty
|
|
if (prev && prev->flags == BlockFlag_Empty) {
|
|
const int prev_index = iblock->prev;
|
|
prev->end = iblock->end;
|
|
prev->next = iblock->next;
|
|
if (next)
|
|
next->prev = iblock->prev;
|
|
|
|
poolFree(&pool->blocks, index);
|
|
index = prev_index;
|
|
iblock = prev;
|
|
}
|
|
|
|
// join with next block if empty
|
|
if (next && next->flags == BlockFlag_Empty) {
|
|
const int next_index = iblock->next;
|
|
|
|
const int next_next_index = next->next;
|
|
block_t *next_next = next_next_index >=0 ? poolGet(&pool->blocks, next_next_index) : NULL;
|
|
|
|
iblock->end = next->end;
|
|
iblock->next = next_next_index;
|
|
if (next_next)
|
|
next_next->prev = index;
|
|
|
|
poolFree(&pool->blocks, next_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(ALOLCATOR_TEST)
|
|
uint32_t rand_pcg32(uint32_t max) {
|
|
if (!max) return 0;
|
|
#define PCG32_INITIALIZER { 0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL }
|
|
static struct { uint64_t state; uint64_t inc; } rng = PCG32_INITIALIZER;
|
|
uint64_t oldstate = rng.state;
|
|
// Advance internal state
|
|
rng.state = oldstate * 6364136223846793005ULL + (rng.inc|1);
|
|
// Calculate output function (XSH RR), uses old state for max ILP
|
|
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
|
|
uint32_t rot = oldstate >> 59u;
|
|
uint32_t ret = (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
|
|
return ret % max;
|
|
}
|
|
|
|
int testRandom(int num_allocs, alo_size_t pool_size, alo_size_t max_align, alo_size_t size_spread) {
|
|
struct alo_pool_s *pool = aloPoolCreate(pool_size, num_allocs / 4 + 1, 1);
|
|
alo_block_t *blocks = MALLOC(num_allocs * sizeof(alo_block_t));
|
|
|
|
int allocated = 0;
|
|
for (int i = 0; i < num_allocs; ++i) {
|
|
const alo_size_t align = 1 + rand_pcg32(max_align - 1);
|
|
const alo_size_t size = pool_size / num_allocs - size_spread + rand_pcg32(size_spread * 2);
|
|
blocks[i] = aloPoolAllocate(pool, size, align);
|
|
|
|
if (blocks[i].size == 0) {
|
|
// FIXME assert somehow that we really can't fit anything here
|
|
continue;
|
|
}
|
|
|
|
++allocated;
|
|
|
|
for (int j = 0; j < i; ++j) {
|
|
if (blocks[j].size == 0)
|
|
continue;
|
|
|
|
ASSERT((blocks[j].offset >= (blocks[i].offset + blocks[i].size)) || (blocks[i].offset >= (blocks[j].offset + blocks[j].size)));
|
|
}
|
|
}
|
|
|
|
FREE(blocks);
|
|
aloPoolDestroy(pool);
|
|
return allocated;
|
|
}
|
|
|
|
int test(void) {
|
|
struct alo_pool_s *pool = aloPoolCreate(1000, 5, 1);
|
|
|
|
{
|
|
// Allocate three blocks to fill the memory entirely
|
|
alo_block_t block1 = aloPoolAllocate(pool, 700, 1);
|
|
alo_block_t block2 = aloPoolAllocate(pool, 200, 1);
|
|
alo_block_t block3 = aloPoolAllocate(pool, 100, 1);
|
|
|
|
ASSERT(block1.offset == 0);
|
|
ASSERT(block1.size == 700);
|
|
|
|
ASSERT(block2.offset == 700);
|
|
ASSERT(block2.size == 200);
|
|
|
|
ASSERT(block3.offset == 900);
|
|
ASSERT(block3.size == 100);
|
|
|
|
// Delete an realloc the block in the middle
|
|
aloPoolFree(pool, block2.index);
|
|
block2 = aloPoolAllocate(pool, 150, 1);
|
|
ASSERT(block2.offset == 700);
|
|
ASSERT(block2.size == 150);
|
|
|
|
// Delete the first block
|
|
aloPoolFree(pool, block1.index);
|
|
block1 = aloPoolAllocate(pool, 650, 1);
|
|
ASSERT(block1.offset == 0);
|
|
ASSERT(block1.size == 650);
|
|
|
|
// Delete the last block
|
|
aloPoolFree(pool, block3.index);
|
|
block3 = aloPoolAllocate(pool, 80, 1);
|
|
ASSERT(block3.offset == 850);
|
|
ASSERT(block3.size == 80);
|
|
|
|
aloPoolFree(pool, block1.index);
|
|
aloPoolFree(pool, block2.index);
|
|
aloPoolFree(pool, block3.index);
|
|
|
|
block1 = aloPoolAllocate(pool, 1000, 1);
|
|
ASSERT(block1.offset == 0);
|
|
ASSERT(block1.size == 1000);
|
|
aloPoolFree(pool, block1.index);
|
|
}
|
|
|
|
{
|
|
// Allocate many small blocks
|
|
alo_block_t b[10];
|
|
for (int i = 0; i < 10; ++i) {
|
|
b[i] = aloPoolAllocate(pool, 100, 1);
|
|
ASSERT(b[i].size == 100);
|
|
ASSERT(b[i].offset == 100*i);
|
|
}
|
|
|
|
{
|
|
// Ensure the pool is full
|
|
alo_block_t fail = aloPoolAllocate(pool, 100, 1);
|
|
ASSERT(fail.size == 0);
|
|
}
|
|
|
|
// free some blocks in a specific order
|
|
aloPoolFree(pool, b[2].index);
|
|
aloPoolFree(pool, b[4].index);
|
|
aloPoolFree(pool, b[3].index);
|
|
|
|
// allocate in the hole
|
|
{
|
|
alo_block_t block1, block2 = aloPoolAllocate(pool, 300, 1);
|
|
ASSERT(block2.size == 300);
|
|
ASSERT(block2.offset == 200);
|
|
|
|
aloPoolFree(pool, b[7].index);
|
|
aloPoolFree(pool, b[6].index);
|
|
aloPoolFree(pool, b[5].index);
|
|
|
|
block1 = aloPoolAllocate(pool, 300, 1);
|
|
ASSERT(block1.size == 300);
|
|
ASSERT(block1.offset == 500);
|
|
|
|
aloPoolFree(pool, block1.index);
|
|
aloPoolFree(pool, b[8].index);
|
|
aloPoolFree(pool, b[9].index);
|
|
aloPoolFree(pool, block2.index);
|
|
|
|
block1 = aloPoolAllocate(pool, 800, 1);
|
|
ASSERT(block1.size == 800);
|
|
ASSERT(block1.offset == 200);
|
|
|
|
aloPoolFree(pool, b[0].index);
|
|
aloPoolFree(pool, b[1].index);
|
|
aloPoolFree(pool, block1.index);
|
|
|
|
block1 = aloPoolAllocate(pool, 1000, 1);
|
|
ASSERT(block1.size == 1000);
|
|
ASSERT(block1.offset == 0);
|
|
aloPoolFree(pool, block1.index);
|
|
}
|
|
}
|
|
|
|
// Alignment
|
|
{
|
|
alo_block_t b[6];
|
|
b[0] = aloPoolAllocate(pool, 5, 1);
|
|
ASSERT(b[0].size == 5);
|
|
ASSERT(b[0].offset == 0);
|
|
|
|
b[1] = aloPoolAllocate(pool, 19, 4);
|
|
ASSERT(b[1].size == 19);
|
|
ASSERT(b[1].offset == 8);
|
|
|
|
b[2] = aloPoolAllocate(pool, 39, 16);
|
|
ASSERT(b[2].size == 39);
|
|
ASSERT(b[2].offset == 32);
|
|
|
|
b[3] = aloPoolAllocate(pool, 200, 128);
|
|
ASSERT(b[3].size == 200);
|
|
ASSERT(b[3].offset == 128);
|
|
|
|
b[4] = aloPoolAllocate(pool, 488, 512);
|
|
ASSERT(b[4].size == 488);
|
|
ASSERT(b[4].offset == 512);
|
|
|
|
b[5] = aloPoolAllocate(pool, 200, 256);
|
|
ASSERT(b[5].size == 0);
|
|
|
|
aloPoolFree(pool, b[3].index);
|
|
|
|
b[5] = aloPoolAllocate(pool, 200, 256);
|
|
ASSERT(b[5].size == 200);
|
|
ASSERT(b[5].offset == 256);
|
|
}
|
|
|
|
aloPoolDestroy(pool);
|
|
return 0;
|
|
}
|
|
|
|
int main(void) {
|
|
test();
|
|
|
|
ASSERT(1000 == testRandom(1000, 1000000, 1, 0));
|
|
testRandom(1000, 1000000, 32, 999);
|
|
return 0;
|
|
}
|
|
#endif
|