Small improvements of the LF MPMC queue Making the LF MPMC queue generic and in a seperate header file
823 lines
22 KiB
C
823 lines
22 KiB
C
#pragma once
|
|
|
|
#include "arena.h"
|
|
/* ============================================================
|
|
Helper functions
|
|
============================================================ */
|
|
|
|
/* Page size */
|
|
static u32 g_pagesize = 0;
|
|
|
|
static inline u32 arena_pagesize(void) {
|
|
if (g_pagesize == 0) {
|
|
g_pagesize = plat_get_pagesize();
|
|
}
|
|
return g_pagesize;
|
|
}
|
|
|
|
mem_arena *arena_block_from_pos(mem_arena *arena, u64 global_pos) {
|
|
mem_arena *selected = arena;
|
|
while (selected) {
|
|
if (global_pos >= selected->base_pos && global_pos <= selected->pos) {
|
|
return selected;
|
|
}
|
|
selected = selected->prev;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
mem_arena *arena_block_from_index(mem_arena *arena, u64 index) {
|
|
if (arena->push_size <= 0) {
|
|
return NULL;
|
|
}
|
|
mem_arena *selected = arena;
|
|
u64 pos = (index - 1) * arena->push_size + arena->block_index - 1;
|
|
while (selected) {
|
|
if (pos >= selected->base_pos && pos < selected->pos) {
|
|
return selected;
|
|
}
|
|
selected = selected->prev;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
mem_arena *arena_block_from_ptr(mem_arena *arena, u8 *ptr) {
|
|
mem_arena *selected = arena;
|
|
|
|
while (selected) {
|
|
u8 *block_base =
|
|
(u8 *)selected + ALIGN_UP_POW2(sizeof(mem_arena), selected->align);
|
|
u8 *block_end = block_base + selected->pos - selected->base_pos;
|
|
|
|
if (ptr >= block_base && ptr < block_end) {
|
|
return selected;
|
|
}
|
|
selected = selected->prev;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
u64 arena_pos_from_ptr(mem_arena *arena, void *ptr) {
|
|
ASSERT(arena);
|
|
ASSERT(ptr);
|
|
if (!arena || !ptr) {
|
|
return -1;
|
|
}
|
|
|
|
mem_arena *owner = arena_block_from_ptr(arena, ptr);
|
|
ASSERT(owner);
|
|
if (!owner) {
|
|
return -1;
|
|
}
|
|
|
|
u8 *block_base = (u8 *)owner + ALIGN_UP_POW2(sizeof(mem_arena), owner->align);
|
|
u64 local_offset = (u8 *)ptr - block_base;
|
|
|
|
u64 pos = owner->base_pos + local_offset;
|
|
|
|
if (pos >= owner->pos) {
|
|
return -1;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
void *arena_ptr_from_pos(mem_arena *arena, u64 global_pos) {
|
|
ASSERT(arena);
|
|
ASSERT(global_pos >= 0);
|
|
if (!arena || global_pos < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
mem_arena *owner = arena_block_from_pos(arena, global_pos);
|
|
if (!owner) {
|
|
return NULL;
|
|
}
|
|
|
|
u8 *block_base = (u8 *)owner + ALIGN_UP_POW2(sizeof(mem_arena), owner->align);
|
|
u64 local_offset = global_pos - owner->base_pos;
|
|
|
|
return (void *)(block_base + local_offset);
|
|
}
|
|
|
|
void *arena_ptr_from_index(mem_arena *arena, u64 index) {
|
|
ASSERT(arena);
|
|
ASSERT(index);
|
|
if (!arena || !index || arena->push_size <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
mem_arena *owner = arena_block_from_index(arena, index);
|
|
if (!owner) {
|
|
return NULL;
|
|
}
|
|
|
|
u8 *block_base = (u8 *)owner + ALIGN_UP_POW2(sizeof(mem_arena), owner->align);
|
|
u64 pos = (index - 1) * owner->push_size + owner->block_index - 1;
|
|
u64 local_offset = pos - owner->base_pos;
|
|
|
|
return (void *)(block_base + local_offset);
|
|
}
|
|
|
|
/* ============================================================
|
|
Arena creation / destruction
|
|
============================================================ */
|
|
|
|
mem_arena *arena_create(arena_params *params) { // mk create
|
|
ASSERT(params);
|
|
ASSERT(params->reserve_size > 0);
|
|
if (!params) {
|
|
return NULL;
|
|
}
|
|
|
|
u32 pagesize = arena_pagesize();
|
|
|
|
u64 reserve_size = ALIGN_UP_POW2(params->reserve_size, pagesize);
|
|
u64 commit_size =
|
|
params->commit_size ? ALIGN_UP_POW2(params->commit_size, pagesize) : 0;
|
|
|
|
ASSERT(commit_size <= reserve_size);
|
|
|
|
mem_arena *arena = (mem_arena *)plat_mem_reserve(reserve_size);
|
|
if (!arena)
|
|
return NULL;
|
|
|
|
/* ------------------------------------------------------------
|
|
Commit policy
|
|
------------------------------------------------------------ */
|
|
|
|
u64 header_commit = ALIGN_UP_POW2(sizeof(mem_arena), pagesize);
|
|
|
|
if (params->commit_policy == ARENA_COMMIT_FULL) {
|
|
/* Commit everything */
|
|
if (!plat_mem_commit(arena, reserve_size)) {
|
|
plat_mem_release(arena, reserve_size);
|
|
return NULL;
|
|
}
|
|
commit_size = reserve_size;
|
|
} else {
|
|
/* Lazy / partial commit */
|
|
if (commit_size == 0 || commit_size < header_commit) {
|
|
commit_size = header_commit;
|
|
}
|
|
|
|
if (!plat_mem_commit(arena, commit_size)) {
|
|
plat_mem_release(arena, reserve_size);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Initialize header */
|
|
arena->prev = NULL;
|
|
arena->next = NULL;
|
|
|
|
arena->base_pos = 0;
|
|
arena->pos = 0;
|
|
|
|
arena->reserve_size = reserve_size;
|
|
arena->commit_size = commit_size;
|
|
arena->commit_pos = commit_size;
|
|
|
|
arena->align = params->align;
|
|
arena->push_size = 0;
|
|
|
|
arena->allow_free_list = params->allow_free_list;
|
|
arena->free_list = NULL;
|
|
arena->allow_swapback = 0;
|
|
|
|
arena->growth_policy = params->growth_policy;
|
|
arena->commit_policy = params->commit_policy;
|
|
|
|
arena->max_nbre_blocks = params->max_nbre_blocks;
|
|
arena->block_index = 1;
|
|
|
|
if (arena->allow_free_list) {
|
|
arena->push_size = ALIGN_UP_POW2(params->push_size, arena->align);
|
|
|
|
if (!params->free_list) {
|
|
arena->free_list = arena_create(&(arena_params){
|
|
.reserve_size = MiB(1),
|
|
.commit_size = MiB(1),
|
|
.align = ARENA_ALIGN,
|
|
.push_size = sizeof(arena_free_node),
|
|
.allow_free_list = false,
|
|
.free_list = NULL,
|
|
.allow_swapback = true,
|
|
.growth_policy = ARENA_GROWTH_NORMAL,
|
|
.commit_policy = ARENA_COMMIT_LAZY,
|
|
.max_nbre_blocks = 0,
|
|
});
|
|
|
|
if (!arena->free_list) {
|
|
plat_mem_release(arena, arena->reserve_size);
|
|
return NULL;
|
|
}
|
|
} else {
|
|
arena->free_list = params->free_list;
|
|
}
|
|
} else {
|
|
ASSERT(params->push_size > 0);
|
|
arena->push_size = ALIGN_UP_POW2(params->push_size, arena->align);
|
|
arena->allow_swapback = params->allow_swapback;
|
|
}
|
|
|
|
return arena;
|
|
}
|
|
|
|
void *arena_destroy(mem_arena **arena_ptr) { // mk destroy
|
|
mem_arena *arena = *arena_ptr;
|
|
if (!arena)
|
|
return NULL;
|
|
|
|
if (arena->free_list) {
|
|
arena_destroy(&arena->free_list);
|
|
}
|
|
|
|
mem_arena *selected = arena;
|
|
while (selected) {
|
|
plat_mem_release(selected, selected->reserve_size);
|
|
selected = selected->prev;
|
|
}
|
|
*arena_ptr = NULL;
|
|
return (void *)1;
|
|
}
|
|
|
|
/* ============================================================
|
|
Allocation
|
|
============================================================ */
|
|
|
|
void *arena_push(mem_arena **arena_ptr, u64 size, bool zero) { // mk push
|
|
ASSERT(arena_ptr);
|
|
if (!arena_ptr)
|
|
return NULL;
|
|
|
|
mem_arena *arena = *arena_ptr;
|
|
|
|
/* ------------------------------------------------------------
|
|
Resolve allocation size
|
|
------------------------------------------------------------ */
|
|
|
|
if (arena->push_size > 0) {
|
|
size = arena->push_size;
|
|
} else {
|
|
if (size == 0)
|
|
return NULL;
|
|
size = ALIGN_UP_POW2(size, arena->align);
|
|
}
|
|
|
|
/* ------------------------------------------------------------
|
|
Free-list reuse (pointer mode)
|
|
------------------------------------------------------------ */
|
|
|
|
if (arena->free_list) {
|
|
mem_arena *fl = arena->free_list;
|
|
u64 fl_base = ALIGN_UP_POW2(sizeof(mem_arena), fl->align);
|
|
u64 count = fl->pos / fl->push_size;
|
|
|
|
b32 arena_full = arena->max_nbre_blocks > 0 &&
|
|
arena->block_index >= arena->max_nbre_blocks;
|
|
|
|
/* -------- Fixed-size: O(1) reuse -------- */
|
|
if (arena->push_size > 0 && count > 0) {
|
|
arena_free_node *node =
|
|
(arena_free_node *)((u8 *)fl + fl_base +
|
|
(count - 1) * sizeof(arena_free_node));
|
|
|
|
mem_arena *owner = arena_block_from_pos(arena, node->offset);
|
|
ASSERT(owner);
|
|
|
|
void *result = (u8 *)owner +
|
|
ALIGN_UP_POW2(sizeof(mem_arena), owner->align) +
|
|
(node->offset - owner->base_pos);
|
|
|
|
arena_pop(&arena->free_list);
|
|
|
|
if (zero)
|
|
memset(result, 0, size);
|
|
return result;
|
|
}
|
|
|
|
/* -------- Variable-size reuse -------- */
|
|
if (arena->push_size == 0 && count > 0 &&
|
|
(arena_full || arena->max_nbre_blocks == 0)) {
|
|
|
|
for (mem_arena *selected_fl = fl; selected_fl;
|
|
selected_fl = selected_fl->prev) {
|
|
|
|
u64 selected_fl_count =
|
|
(selected_fl->pos - selected_fl->base_pos) / selected_fl->push_size;
|
|
|
|
for (u64 j = selected_fl_count; j >= 1; j--) {
|
|
arena_free_node *node =
|
|
(arena_free_node *)((u8 *)selected_fl + fl_base +
|
|
(j - 1) * sizeof(arena_free_node));
|
|
|
|
if (node->size >= size) {
|
|
mem_arena *owner = arena_block_from_pos(arena, node->offset);
|
|
ASSERT(owner);
|
|
|
|
u8 *result = (u8 *)owner +
|
|
ALIGN_UP_POW2(sizeof(mem_arena), owner->align) +
|
|
(node->offset - owner->base_pos);
|
|
|
|
u64 remaining = node->size - size;
|
|
u64 rem_offset = node->offset + size;
|
|
|
|
arena_swapback_pop(&arena->free_list, count);
|
|
|
|
if (remaining > 0) {
|
|
arena_free_node *rem =
|
|
(arena_free_node *)arena_push(&arena->free_list, 0, true);
|
|
if (!rem)
|
|
return NULL;
|
|
|
|
assert(rem);
|
|
|
|
rem->offset = rem_offset;
|
|
rem->size = remaining;
|
|
}
|
|
|
|
if (zero)
|
|
memset(result, 0, size);
|
|
return result;
|
|
}
|
|
count--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------
|
|
Normal allocation (last block)
|
|
------------------------------------------------------------ */
|
|
|
|
mem_arena *selected = arena;
|
|
|
|
u64 local_pos = selected->pos - selected->base_pos;
|
|
u64 local_pre = ALIGN_UP_POW2(local_pos, selected->align);
|
|
u64 local_post = local_pre + size;
|
|
|
|
if (local_post > selected->reserve_size -
|
|
ALIGN_UP_POW2(sizeof(mem_arena), selected->align)) {
|
|
|
|
if (arena->allow_free_list && arena->push_size == 0) {
|
|
u64 tail_start = selected->pos;
|
|
u64 tail_size = selected->reserve_size -
|
|
(tail_start - selected->base_pos +
|
|
ALIGN_UP_POW2(sizeof(mem_arena), selected->align));
|
|
|
|
if (tail_size > 0) {
|
|
arena_free_node *node =
|
|
(arena_free_node *)arena_push(&arena->free_list, 0, true);
|
|
assert(node);
|
|
if (!node)
|
|
return NULL;
|
|
|
|
node->offset = tail_start;
|
|
node->size = tail_size;
|
|
selected->pos = selected->base_pos + selected->reserve_size;
|
|
}
|
|
}
|
|
|
|
if (selected->next) {
|
|
selected->next->base_pos = selected->pos + 1;
|
|
selected->next->pos = selected->pos + 1;
|
|
selected->next->free_list = selected->free_list;
|
|
selected = selected->next;
|
|
*arena_ptr = selected;
|
|
local_pre = 0;
|
|
local_post = size;
|
|
} else {
|
|
|
|
/* ------------------------------------------------------------
|
|
Grow arena if needed
|
|
------------------------------------------------------------ */
|
|
|
|
if (arena->max_nbre_blocks &&
|
|
arena->block_index >= arena->max_nbre_blocks) {
|
|
printf("Arena full.\n");
|
|
return NULL;
|
|
}
|
|
|
|
u64 new_reserve = selected->reserve_size;
|
|
if (arena->growth_policy == ARENA_GROWTH_DOUBLE)
|
|
new_reserve *= 2;
|
|
|
|
arena_params p = {
|
|
.reserve_size = new_reserve,
|
|
.commit_size = selected->commit_size,
|
|
.align = selected->align,
|
|
.push_size = arena->push_size,
|
|
.allow_free_list = arena->allow_free_list,
|
|
.free_list = arena->free_list,
|
|
.allow_swapback = arena->allow_swapback,
|
|
.growth_policy = arena->growth_policy,
|
|
.commit_policy = arena->commit_policy,
|
|
.max_nbre_blocks = arena->max_nbre_blocks,
|
|
};
|
|
|
|
mem_arena *next = arena_create(&p);
|
|
if (!next)
|
|
return NULL;
|
|
|
|
next->base_pos = selected->pos + 1;
|
|
next->pos = selected->pos + 1;
|
|
next->prev = selected;
|
|
selected->next = next;
|
|
next->block_index = selected->block_index + 1;
|
|
|
|
selected = next;
|
|
*arena_ptr = selected;
|
|
|
|
local_pre = 0;
|
|
local_post = size;
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------
|
|
Commit memory if needed
|
|
------------------------------------------------------------ */
|
|
|
|
if (local_post > selected->commit_pos) {
|
|
u64 new_commit = ALIGN_UP_POW2(local_post, arena_pagesize());
|
|
new_commit = MIN(new_commit, selected->reserve_size);
|
|
|
|
if (!plat_mem_commit((u8 *)selected + selected->commit_pos,
|
|
new_commit - selected->commit_pos)) {
|
|
return NULL;
|
|
}
|
|
|
|
selected->commit_pos = new_commit;
|
|
}
|
|
|
|
/* ------------------------------------------------------------
|
|
Finalize allocation
|
|
------------------------------------------------------------ */
|
|
|
|
u8 *result = (u8 *)selected +
|
|
ALIGN_UP_POW2(sizeof(mem_arena), selected->align) + local_pre;
|
|
|
|
selected->pos = selected->base_pos + local_post;
|
|
|
|
if (zero)
|
|
memset(result, 0, size);
|
|
|
|
return result;
|
|
}
|
|
|
|
/* ============================================================
|
|
Free (pointer mode): pop to free list
|
|
============================================================ */
|
|
|
|
void *arena_free(mem_arena **arena_ptr, u8 **ptr, u64 size) { // mk free
|
|
|
|
mem_arena *arena = *arena_ptr;
|
|
|
|
ASSERT(arena);
|
|
ASSERT(arena->allow_free_list);
|
|
ASSERT(ptr && *ptr);
|
|
|
|
if (!arena || !arena->allow_free_list || !ptr || !*ptr)
|
|
return NULL;
|
|
|
|
u64 elem_size = arena->push_size ? arena->push_size : size;
|
|
ASSERT(elem_size > 0);
|
|
|
|
/* ------------------------------------------------------------
|
|
Find owning block
|
|
------------------------------------------------------------ */
|
|
|
|
mem_arena *selected = arena;
|
|
mem_arena *owner = arena_block_from_ptr(arena, *ptr);
|
|
ASSERT(owner);
|
|
if (!owner) {
|
|
return NULL;
|
|
}
|
|
|
|
/* ------------------------------------------------------------
|
|
Compute global offset using arena_pos()
|
|
------------------------------------------------------------ */
|
|
|
|
u64 global_offset = arena_pos_from_ptr(arena, *ptr);
|
|
if (global_offset == -1) {
|
|
return NULL;
|
|
}
|
|
|
|
/* ------------------------------------------------------------
|
|
Fast path: pop only if this is the LAST block
|
|
------------------------------------------------------------ */
|
|
|
|
if (owner == arena && global_offset + elem_size == arena->pos) {
|
|
arena->pos -= elem_size;
|
|
if (arena->pos < arena->base_pos) {
|
|
arena->prev->pos = arena->pos - 1;
|
|
arena->pos = arena->base_pos;
|
|
arena->prev->free_list = arena->free_list;
|
|
*arena_ptr = arena->prev;
|
|
}
|
|
*ptr = NULL;
|
|
return (void *)1;
|
|
}
|
|
|
|
/* ------------------------------------------------------------
|
|
Otherwise push into free list
|
|
------------------------------------------------------------ */
|
|
|
|
arena_free_node *node = (arena_free_node *)arena_push(
|
|
&arena->free_list, sizeof(arena_free_node), false);
|
|
if (!node)
|
|
return NULL;
|
|
|
|
node->offset = global_offset;
|
|
node->size = elem_size;
|
|
|
|
*ptr = NULL;
|
|
return (void *)1;
|
|
}
|
|
|
|
/* ============================================================
|
|
Stack operations
|
|
============================================================ */
|
|
|
|
void *arena_pop_to(mem_arena **arena_ptr, u64 count) { // mk pop to
|
|
ASSERT(arena_ptr);
|
|
|
|
mem_arena *arena = *arena_ptr;
|
|
|
|
ASSERT(!arena->allow_free_list);
|
|
ASSERT(arena->push_size > 0);
|
|
if (arena->allow_free_list || arena->push_size <= 0) {
|
|
return NULL;
|
|
}
|
|
|
|
u64 target_pos;
|
|
if (arena->pos < count * arena->push_size) {
|
|
target_pos = 0;
|
|
} else {
|
|
target_pos = arena->pos - count * arena->push_size;
|
|
}
|
|
|
|
mem_arena *selected = arena;
|
|
|
|
while (true) {
|
|
if (selected->base_pos <= target_pos) {
|
|
selected->pos = target_pos;
|
|
selected->free_list = arena->free_list;
|
|
break;
|
|
}
|
|
selected->pos = selected->base_pos;
|
|
target_pos = target_pos - 1;
|
|
selected = selected->prev;
|
|
}
|
|
*arena_ptr = selected;
|
|
|
|
return (void *)1;
|
|
}
|
|
|
|
void *arena_swapback_pop(mem_arena **arena_ptr, u64 index) { // mk swapback
|
|
|
|
mem_arena *arena = *arena_ptr;
|
|
|
|
ASSERT(arena);
|
|
ASSERT(arena->push_size > 0);
|
|
ASSERT(arena->allow_swapback);
|
|
if (arena->push_size <= 0 || !arena->allow_swapback) {
|
|
return NULL;
|
|
}
|
|
|
|
u64 count = arena->pos / arena->push_size;
|
|
ASSERT(index <= count);
|
|
if (index > count) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Last element: just pop */
|
|
if (index == count - 1) {
|
|
u8 *r = arena_pop(&arena);
|
|
|
|
*arena_ptr = arena;
|
|
|
|
return r;
|
|
}
|
|
|
|
mem_arena *owner = arena_block_from_index(arena, index);
|
|
|
|
if (!owner) {
|
|
fprintf(stderr, "ERROR: Swapback pop failed, index out of range");
|
|
return NULL;
|
|
}
|
|
u8 *owner_base = (u8 *)owner + ALIGN_UP_POW2(sizeof(mem_arena), owner->align);
|
|
u8 *arena_base = (u8 *)arena + ALIGN_UP_POW2(sizeof(mem_arena), arena->align);
|
|
|
|
u8 *dst = arena_ptr_from_index(arena, index);
|
|
u8 *src = arena_ptr_from_index(arena, count);
|
|
|
|
memcpy(dst, src, arena->push_size);
|
|
|
|
u8 *r = arena_pop(&arena);
|
|
|
|
*arena_ptr = arena;
|
|
|
|
return r;
|
|
}
|
|
|
|
/* ============================================================
|
|
Utilities
|
|
============================================================ */
|
|
|
|
void *arena_clear(mem_arena **arena_ptr) { // mk clear
|
|
|
|
mem_arena *arena = *arena_ptr;
|
|
|
|
if (!arena)
|
|
return NULL;
|
|
|
|
mem_arena *selected = arena;
|
|
while (selected->prev) {
|
|
selected->pos = 0;
|
|
selected->base_pos = 0;
|
|
selected = selected->prev;
|
|
}
|
|
selected->pos = 0;
|
|
selected->free_list = arena->free_list;
|
|
|
|
*arena_ptr = selected;
|
|
|
|
if (arena->free_list) {
|
|
if (!arena_clear(&arena->free_list))
|
|
return NULL;
|
|
}
|
|
return (void *)1;
|
|
}
|
|
|
|
mem_arena *arena_merge(mem_arena **dst_ptr, mem_arena **src_ptr) { // mk merge
|
|
|
|
mem_arena *dst = *dst_ptr;
|
|
mem_arena *src = *src_ptr;
|
|
|
|
if (!dst || !src || dst == src)
|
|
return NULL;
|
|
|
|
/* ------------------------------------------------------------
|
|
Config compatibility
|
|
------------------------------------------------------------ */
|
|
|
|
if (dst->align != src->align || dst->push_size != src->push_size ||
|
|
dst->allow_free_list != src->allow_free_list ||
|
|
dst->allow_swapback != src->allow_swapback ||
|
|
dst->growth_policy != src->growth_policy) {
|
|
return NULL;
|
|
}
|
|
|
|
/* ------------------------------------------------------------
|
|
Merge free lists
|
|
------------------------------------------------------------ */
|
|
u64 block_base_pos = dst->pos + 1;
|
|
|
|
if (dst->allow_free_list) {
|
|
u64 fl_base = ALIGN_UP_POW2(sizeof(mem_arena), src->free_list->align);
|
|
|
|
for (mem_arena *selected_fl = src->free_list; selected_fl;
|
|
selected_fl = selected_fl->prev) {
|
|
|
|
u64 selected_fl_count =
|
|
(selected_fl->pos - selected_fl->base_pos) / selected_fl->push_size;
|
|
|
|
for (u64 j = selected_fl_count; j >= 1; j--) {
|
|
arena_free_node *node =
|
|
(arena_free_node *)((u8 *)selected_fl + fl_base +
|
|
(j - 1) * sizeof(arena_free_node));
|
|
node->offset += block_base_pos;
|
|
}
|
|
}
|
|
|
|
src->free_list = arena_merge(&dst->free_list, &src->free_list);
|
|
if (!src->free_list)
|
|
return NULL;
|
|
}
|
|
|
|
/* ------------------------------------------------------------
|
|
Walk src blocks once:
|
|
------------------------------------------------------------ */
|
|
|
|
mem_arena *src_first = NULL;
|
|
|
|
for (mem_arena *b = src; b; b = b->prev) {
|
|
src_first = b;
|
|
}
|
|
/* ------------------------------------------------------------
|
|
Update global metadata
|
|
------------------------------------------------------------ */
|
|
|
|
u8 selected_block_index = dst->block_index;
|
|
dst->max_nbre_blocks += src->max_nbre_blocks;
|
|
|
|
mem_arena *selected = src_first;
|
|
while (selected) {
|
|
selected->pos += block_base_pos;
|
|
selected->base_pos += block_base_pos;
|
|
selected->block_index += selected_block_index;
|
|
selected->max_nbre_blocks = dst->max_nbre_blocks;
|
|
selected = selected->next;
|
|
}
|
|
|
|
for (mem_arena *a = dst; a; a = a->prev) {
|
|
a->max_nbre_blocks = dst->max_nbre_blocks;
|
|
}
|
|
|
|
if (dst->next) {
|
|
mem_arena *src_last = NULL;
|
|
for (mem_arena *b = src; b; b = b->next) {
|
|
src_last = b;
|
|
}
|
|
|
|
mem_arena *dst_fst_empty_block = dst->next;
|
|
|
|
mem_arena *dst_empty_block = dst_fst_empty_block;
|
|
u8 dst_empty_block_index = src_last->block_index;
|
|
while (dst_empty_block) {
|
|
dst_empty_block->block_index = dst_empty_block_index + 1;
|
|
dst_empty_block->max_nbre_blocks = dst->max_nbre_blocks;
|
|
dst_empty_block = dst_empty_block->next;
|
|
}
|
|
|
|
/* ------------------------------------------------------------
|
|
Stitch block chains
|
|
------------------------------------------------------------ */
|
|
|
|
src_last->next = dst_fst_empty_block;
|
|
dst_fst_empty_block->prev = src_last;
|
|
}
|
|
|
|
dst->next = src_first;
|
|
src_first->prev = dst;
|
|
|
|
*dst_ptr = src;
|
|
*src_ptr = NULL;
|
|
|
|
return *dst_ptr;
|
|
}
|
|
|
|
/* ============================================================
|
|
Temp arenas
|
|
============================================================ */
|
|
|
|
mem_arena_temp arena_temp_begin(mem_arena *arena) {
|
|
ASSERT(arena);
|
|
ASSERT(!arena->allow_free_list);
|
|
return (mem_arena_temp){arena, arena->pos};
|
|
}
|
|
|
|
void arena_temp_end(mem_arena_temp temp) {
|
|
ASSERT(temp.arena);
|
|
ASSERT(!temp.arena->allow_free_list);
|
|
arena_pop_to(&temp.arena, temp.pos / temp.arena->push_size);
|
|
}
|
|
|
|
static THREAD_LOCAL mem_arena *_scratch_arenas[2] = {NULL, NULL};
|
|
|
|
mem_arena_temp arena_scratch_get(mem_arena **conflicts, u32 num_conflicts) {
|
|
i32 scratch_index = -1;
|
|
|
|
for (i32 i = 0; i < 2; i++) {
|
|
b32 conflict_found = false;
|
|
|
|
for (u32 j = 0; j < num_conflicts; j++) {
|
|
if (_scratch_arenas[i] == conflicts[j]) {
|
|
conflict_found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!conflict_found) {
|
|
scratch_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (scratch_index == -1) {
|
|
return (mem_arena_temp){0};
|
|
}
|
|
|
|
mem_arena **selected = &_scratch_arenas[scratch_index];
|
|
|
|
if (*selected == NULL) {
|
|
arena_params params = {
|
|
.reserve_size = MiB(64),
|
|
.commit_size = MiB(1),
|
|
.align = ARENA_ALIGN,
|
|
.push_size = 8,
|
|
.allow_free_list = false,
|
|
.allow_swapback = true,
|
|
.growth_policy = ARENA_GROWTH_NORMAL,
|
|
.commit_policy = ARENA_COMMIT_LAZY,
|
|
.max_nbre_blocks = 0,
|
|
};
|
|
|
|
*selected = arena_create(¶ms);
|
|
}
|
|
|
|
return arena_temp_begin(*selected);
|
|
}
|
|
|
|
void arena_scratch_release(mem_arena_temp scratch) { arena_temp_end(scratch); }
|