power_play/src/base/base_buddy.c
2025-07-29 20:56:33 -05:00

215 lines
5.5 KiB
C

/* TODO: Elminiate meta arena. Just store levels in first 4096 bytes of buddy arena, and then zone header data at the beginning of each allocation. */
////////////////////////////////
//~ Buddy ctx
BuddyCtx *AllocBuddyCtx(u64 reserve)
{
/* TODO: Determine meta reserve dynamically */
Arena *meta_arena = AllocArena(Gibi(64));
BuddyCtx *ctx = PushStruct(meta_arena, BuddyCtx);
ctx->meta_arena = meta_arena;
ctx->data_arena = AllocArena(reserve);
/* TODO: Minimum block size */
ctx->levels = PushArray(ctx->meta_arena, BuddyLevel, 64);
for (u64 i = 0; i < 64; ++i)
{
BuddyLevel *level = &ctx->levels[i];
level->ctx = ctx;
level->tier = i;
level->size = (u64)1 << i;
}
return ctx;
}
void ReleaseBuddyCtx(BuddyCtx *ctx)
{
ReleaseArena(ctx->data_arena);
ReleaseArena(ctx->meta_arena);
}
////////////////////////////////
//~ Block alloc / release
BuddyBlock *AllocBuddyBlock(BuddyCtx *ctx, u64 size)
{
if (size > 0x00FFFFFFFFFFFFFFULL)
{
/* TODO: Error */
Assert(0);
}
/* TODO: Minimum block size */
/* TODO: Faster MSB calculation */
u64 desired_block_size = 1;
u64 desired_level_tier = 0;
while (desired_block_size < size && desired_level_tier < 64)
{
desired_block_size <<= 1;
++desired_level_tier;
}
BuddyBlock *block = GetUnusedBuddyBlock(ctx, &ctx->levels[desired_level_tier]);
return block;
}
void ReleaseBuddyBlock(BuddyBlock *block)
{
block->is_used = 0;
BuddyLevel *level = block->level;
BuddyBlock *parent = block->parent;
BuddyBlock *sibling = block->sibling;
if (!sibling->is_used && parent != 0)
{
/* Merge siblings */
BuddyCtx *ctx = level->ctx;
PopBuddyBlock(ctx, level, block);
PopBuddyBlock(ctx, level, sibling);
/* Release parent */
ReleaseBuddyBlock(parent);
}
else
{
if (level->first_unused_block)
{
block->next = level->first_unused_block;
level->first_unused_block->prev = block;
}
level->first_unused_block = block;
}
}
////////////////////////////////
//~ Block push / pop
//- Push
BuddyBlock *PushBuddyBlock(BuddyCtx *ctx)
{
BuddyBlock *block;
if (ctx->first_free_block)
{
block = ctx->first_free_block;
ctx->first_free_block = block->next;
}
else
{
block = PushStructNoZero(ctx->meta_arena, BuddyBlock);
}
MEMZERO_STRUCT(block);
return block;
}
//- Pop
void PopBuddyBlock(BuddyCtx *ctx, BuddyLevel *level, BuddyBlock *block)
{
/* Remove from unused list */
{
BuddyBlock *prev = block->prev;
BuddyBlock *next = block->next;
if (prev)
{
prev->next = next;
}
else
{
level->first_unused_block = next;
}
if (next)
{
next->prev = prev;
}
}
block->next = ctx->first_free_block;
ctx->first_free_block = block;
}
////////////////////////////////
//~ Get unused block
BuddyBlock *GetUnusedBuddyBlock(BuddyCtx *ctx, BuddyLevel *level)
{
BuddyBlock *block = 0;
/* TODO: Tier oob check */
if (level->first_unused_block)
{
block = level->first_unused_block;
level->first_unused_block = block->next;
if (level->first_unused_block)
{
level->first_unused_block->prev = 0;
}
block->is_used = 1;
block->next = 0;
}
else
{
if (level->backed)
{
BuddyLevel *parent_level = &ctx->levels[level->tier + 1];
BuddyBlock *parent_block = GetUnusedBuddyBlock(ctx, parent_level);
/* Create left (used) block from parent block */
BuddyBlock *left = PushBuddyBlock(ctx);
left->is_used = 1;
left->level = level;
left->parent = parent_block;
left->memory = parent_block->memory;
/* Create right (UNUSED) block from parent block */
BuddyBlock *right = PushBuddyBlock(ctx);
right->is_used = 0;
right->level = level;
right->parent = parent_block;
right->memory = left->memory + level->size;
if (level->first_unused_block)
{
right->next = level->first_unused_block;
level->first_unused_block->prev = right;
}
level->first_unused_block = right;
left->sibling = right;
right->sibling = left;
block = left;
}
else
{
Arena *arena = ctx->data_arena;
/* Grow arena */
i64 level_commit_diff = (level->size * 2) - arena->pos;
if (level_commit_diff > 0)
{
PushArrayNoZero(arena, u8, level_commit_diff);
Assert(arena->pos == (level->size * 2));
}
/* Create left (used) block from existing child block memory */
BuddyBlock *left = PushBuddyBlock(ctx);
left->is_used = 1;
left->level = level;
left->memory = ArenaBase(arena);
/* Create right (UNUSED) block from new arena memory */
BuddyBlock *right = PushBuddyBlock(ctx);
right->is_used = 0;
right->level = level;
right->memory = left->memory + level->size;
left->sibling = right;
right->sibling = left;
block = left;
level->backed = 1;
}
}
return block;
}