diff --git a/.nvim.lua b/.nvim.lua new file mode 100644 index 0000000..93e1e08 --- /dev/null +++ b/.nvim.lua @@ -0,0 +1 @@ +vim.opt.makeprg = "./build.sh" diff --git a/src/arena.c b/src/arena.c index 185c180..45d420d 100644 --- a/src/arena.c +++ b/src/arena.c @@ -2,101 +2,142 @@ #include "base_core.h" #include "platform.h" - -MemoryBlock *memoryblock_allocate(size_t size) +typedef struct MemoryBlockOptions MemoryBlockOptions; +struct MemoryBlockOptions { + U32 size; + U32 commit_size; + U32 commit_now_size; + U32 page_size; +}; +static MemoryBlock *memoryblock_allocate(const MemoryBlockOptions *opts) { - void *base = memory_allocate(size); - Assert(base != NULL); + U32 aligned_size = AlignUpPow2(opts->size, opts->page_size); + U32 aligned_commit_size = AlignUpPow2(opts->commit_size, opts->page_size); + U32 aligned_commit_now_size = AlignUpPow2(opts->commit_now_size, opts->page_size); - MemoryBlock *block = (MemoryBlock *)base; - block->base = (U8 *)base; - block->size = size; - block->pos = MemoryBlock_Header_Size; - block->base_pos = 0; - block->prev = NULL; + MemoryBlock *block = memory_reserve(aligned_size); + Assert(block != NULL); + memory_commit(block, aligned_commit_now_size); + block->base = (U8 *)block; + block->size = aligned_size; + block->first_free = MemoryBlock_Header_Size; + block->arena_offset = 0; + block->prev = NULL; + block->committed_end = aligned_commit_now_size; + + ASAN_POISON_MEMORY_REGION(block->base, block->size); + ASAN_UNPOISON_MEMORY_REGION(block, MemoryBlock_Header_Size); return block; } -void arena_init(Arena *arena, const ArenaParams *params) +void arena_init(Arena *arena, const ArenaOptions *opts) { - Assert(params->size > MemoryBlock_Header_Size); - Assert(params->size < (U32_Max - MemoryBlock_Header_Size - 1)); + U32 page_size = platform_page_size(); + Assert(opts->size > MemoryBlock_Header_Size); + Assert(opts->size < (U32_Max - page_size)); // Reserving last piece so our math doesn't overflow + Assert(opts->commit_size > 0); + Assert(opts->commit_size <= opts->size); - MemoryBlock *block = memoryblock_allocate(params->size); - ASAN_UNPOISON_MEMORY_REGION(block, MemoryBlock_Header_Size); + MemoryBlockOptions mb_opts; + mb_opts.page_size = page_size; + mb_opts.commit_size = opts->commit_size; + mb_opts.size = opts->size; + mb_opts.commit_now_size = opts->commit_size; + MemoryBlock *block = memoryblock_allocate(&mb_opts); - arena->flags = params->flags; + arena->flags = opts->flags; arena->checkpoint_level = 0; arena->current = block; - arena->requested_size = params->size; + arena->block_size = opts->size; + arena->commit_size = opts->commit_size; + arena->page_size = page_size; - ASAN_POISON_MEMORY_REGION(&block->base[block->pos], memoryblock_available(block)); } void arena_free(Arena *arena) { MemoryBlock *block = arena->current; - + Assert(arena->checkpoint_level == 0); while (block != NULL) { MemoryBlock *prev = block->prev; memory_free(block->base, block->size); block = prev; } + arena->current = NULL; } void *arena_push(Arena *arena, U32 size, U32 align) { - Assert(size < (U32_Max - align - MemoryBlock_Header_Size - 1)); + Assert(size < (U32_Max - platform_page_size())); Assert(IsPow2(align)); MemoryBlock *block = arena->current; - U32 start_pos = AlignUpPow2(block->pos, align); - U32 available = memoryblock_available(block); + U32 start_pos = AlignUpPow2(block->first_free, align); // might align past block->size, handled in below check + U32 available = block->size - start_pos; - if (available < size) { - U32 new_size = arena->requested_size; // original user requeted size - if (new_size < size) { - new_size = AlignUpPow2(size + MemoryBlock_Header_Size,align); + if (start_pos > block->size || available < size) { + U32 block_size = arena->block_size; // original user requested size + U32 new_commit_size = arena->commit_size; + if (size > block_size - MemoryBlock_Header_Size) { + block_size = AlignUpPow2(size + MemoryBlock_Header_Size,align); + new_commit_size = block_size; } + U32 commit_now_size = Max(new_commit_size, size); - MemoryBlock *new_block = memoryblock_allocate(new_size); - new_block->base_pos = block->base_pos + block->size; + MemoryBlockOptions mb_opts; + mb_opts.page_size = arena->page_size; + mb_opts.commit_size = new_commit_size; + mb_opts.size = block_size; + mb_opts.commit_now_size = commit_now_size; + MemoryBlock *new_block = memoryblock_allocate(&mb_opts); + // arena_offset tracks the virtual absolute position within an arena + // This is not a contiguous space, we use it to free blocks when popping the arena + new_block->arena_offset = block->arena_offset + block->size; new_block->prev = block; arena->current = new_block; block = new_block; - start_pos = AlignUpPow2(block->pos, align); + start_pos = AlignUpPow2(block->first_free, align); } U32 end_pos = start_pos + size; Assert(end_pos <= block->size); - U8 *mem = &block->base[start_pos]; - block->pos = end_pos; + U8 *mem = &block->base[start_pos]; + if (block->committed_end < end_pos) { + // Commit more memory + U32 aligned_end_pos = AlignUpPow2(start_pos + size, arena->page_size); + U32 aligned_size = RoundUpToMultiple(aligned_end_pos - block->committed_end, arena->commit_size); + aligned_size = Min(aligned_size, block->size - block->committed_end); + + memory_commit(&block->base[block->committed_end], aligned_size); + block->committed_end += aligned_size; + Assert(block->committed_end <= block->size); + } + block->first_free = end_pos; ASAN_UNPOISON_MEMORY_REGION(mem, size); return mem; } -void arena_pop_to(Arena *arena, U32 abs_pos) +void arena_reset_to(Arena *arena, U64 total_offset) { MemoryBlock *block = arena->current; - abs_pos = Max(MemoryBlock_Header_Size, abs_pos); + total_offset = Max(MemoryBlock_Header_Size, total_offset); while (block) { MemoryBlock *prev = block->prev; - if (block->base_pos >= abs_pos) { + if (block->arena_offset >= total_offset) { ASAN_UNPOISON_MEMORY_REGION(block->base, block->size); memory_free(block->base, block->size); arena->current = prev; } else { - U32 new_pos = abs_pos - block->base_pos; + U64 new_pos = total_offset - block->arena_offset; new_pos = Max(MemoryBlock_Header_Size, new_pos); - // Note, when doing commit/release, new_pos <= current->pos - // is the right thing . shouldn't be able to pop to larger than what's already commited - Assert(new_pos <= block->size); - block->pos = new_pos; - ASAN_POISON_MEMORY_REGION(&block->base[block->pos], memoryblock_available(block)); + // Disallow arbitrary pops to positions that previously were not commited. + Assert(new_pos <= block->committed_end); + block->first_free = new_pos; + ASAN_POISON_MEMORY_REGION(&block->base[block->first_free], memoryblock_available(block)); break; } @@ -104,25 +145,25 @@ void arena_pop_to(Arena *arena, U32 abs_pos) } } -ArenaCheckPoint arena_temp_begin(Arena *arena) +ArenaCheckpoint arena_temp_begin(Arena *arena) { - ArenaCheckPoint checkpoint = {}; + ArenaCheckpoint checkpoint = {}; checkpoint.arena = arena; - checkpoint.absolute_pos = arena_used(arena); + checkpoint.total_offset = arena_used(arena); arena->checkpoint_level++; return checkpoint; } -void arena_temp_end(ArenaCheckPoint *checkpoint) +void arena_temp_end(ArenaCheckpoint *checkpoint) { Arena *arena = checkpoint->arena; Assert(arena->checkpoint_level > 0); - arena_pop_to(arena, checkpoint->absolute_pos); + arena_reset_to(arena, checkpoint->total_offset); arena->checkpoint_level--; } void arena_reset(Arena *arena) { - arena_pop_to(arena, 0); + arena_reset_to(arena, 0); } diff --git a/src/arena.h b/src/arena.h index 8231515..934c2c8 100644 --- a/src/arena.h +++ b/src/arena.h @@ -2,63 +2,67 @@ #include "base_core.h" -typedef U32 ArenaFlags; // no flags defined yets - -typedef struct ArenaParams ArenaParams; -struct ArenaParams { +typedef U32 ArenaFlags; +typedef struct ArenaOptions ArenaOptions; +struct ArenaOptions { U32 size; + U32 commit_size; ArenaFlags flags; // No flags defined yet }; typedef struct MemoryBlock MemoryBlock; struct MemoryBlock { U8 *base; - U32 size; - U32 base_pos; // absolute position of base relative to the first linked arena - U32 pos; // start of next allocation + U32 size; // Block size, page aligned + U32 first_free; // start of next allocation + U64 arena_offset; // absolute offset of base relative to the first linked arena + U32 committed_end; // end of committed region. Will be page aligned MemoryBlock *prev; }; typedef struct Arena Arena; struct Arena { MemoryBlock *current; - U32 requested_size; + U32 block_size; // requested block size + U32 commit_size; // requested commit size + U32 page_size; // cached page size ArenaFlags flags; S32 checkpoint_level; }; -typedef struct ArenaCheckPoint ArenaCheckPoint; -struct ArenaCheckPoint { +typedef struct ArenaCheckpoint ArenaCheckpoint; +struct ArenaCheckpoint { Arena *arena; - U32 absolute_pos; + U64 total_offset; }; +#ifndef ArenaParams_Default +#define ArenaParams_Default { .size = MB(32), .commit_size = KB(64) } +#endif StaticAssert(sizeof(MemoryBlock) <= 64); +// Try keep start of user memory at a cacheline #define MemoryBlock_Header_Size 64u - - -void arena_init(Arena *arena, const ArenaParams *params); - +void arena_init(Arena *arena, const ArenaOptions *params); void arena_free(Arena *arena); void *arena_push(Arena *arena, U32 size, U32 align); #define arena_push_type(arena, type) arena_push(arena, (U32)sizeof(type), alignof(type)) +// Invalid for arrays > U32 #define arena_push_array_of(arena, type, len) arena_push(arena, (U32) (sizeof(type) * (len)), alignof(type)) -ArenaCheckPoint arena_temp_begin(Arena *arena); -void arena_temp_end(ArenaCheckPoint *checkpoint); -void arena_pop_to(Arena *arena, U32 abs_pos); +ArenaCheckpoint arena_temp_begin(Arena *arena); +void arena_temp_end(ArenaCheckpoint *checkpoint); +void arena_reset_to(Arena *arena, U64 total_offset); void arena_reset(Arena *arena); -MemoryBlock *memoryblock_allocate(size_t size); static INLINE U32 memoryblock_available(const MemoryBlock *block) { - return block->size - block->pos; + return block->size - block->first_free; } -static INLINE U32 arena_used(const Arena *arena) +static INLINE U64 arena_used(const Arena *arena) { const MemoryBlock *block = arena->current; - return block->base_pos + block->pos; + return block->arena_offset + block->first_free; } diff --git a/src/base_core.h b/src/base_core.h index 9205828..ac68e3e 100644 --- a/src/base_core.h +++ b/src/base_core.h @@ -41,10 +41,11 @@ typedef unsigned __int128 U128; #define U32_Max ((U32)0xffffffff) #define U64_Max ((U64)0xffffffffffffffffULL) -#define U8_Min ((U8) 0x80) -#define U16_Min ((U16)0x8000) -#define U32_Min ((U32)0x80000000) -#define U64_Min ((U64)0x8000000000000000ULL) +// Duh .. +#define U8_Min ((U8)0x0) +#define U16_Min ((U16)0x0) +#define U32_Min ((U32)0x0) +#define U64_Min ((U64)0x0) #define S8_Max ((S8) 0x7f) #define S16_Max ((S16)0x7fff) @@ -107,6 +108,7 @@ typedef unsigned __int128 U128; #define AlignDownPow2(val, align) ((val) & ~((align) - 1UL)) #define IsPow2(x) ((x) && !((x) & ((x) - 1UL))) #define IsPow2OrZero(x) (!((x) & ((x) - 1UL))) +#define IsAlignedToPow2(x, y) (IsPow2(y) && !((x) & ((y) - 1UL))) #define GetBit(val, idx) (((val) >> (idx)) & 1) // Get n_bits starting at idx going left @@ -132,7 +134,7 @@ typedef unsigned __int128 U128; #endif /** - * Round up x / y + * Ceiling of X / y , or round up x / y * (e.g 100/9 == 12, 3/2 == 2) * x + y must not overflow. * @@ -140,7 +142,7 @@ typedef unsigned __int128 U128; * @param y denominator * @return x/y rounded up */ -#define DivRoundUp(x, y) (((x) + ((y) - 1)) / (y)) +#define CeilDiv(x, y) (((x) + ((y) - 1)) / (y)) /** * Round x up to nearest multiple of y @@ -151,7 +153,7 @@ typedef unsigned __int128 U128; * @param y denominator * @return x rounded up to nearest multiple of y */ -#define RoundUp(x, y) (DIV_ROUND_UP((x), (y)) * (y)) +#define RoundUpToMultiple(x, y) (CeilDiv((x), (y)) * (y)) /** * Round x down to nearest multiple of y @@ -162,7 +164,7 @@ typedef unsigned __int128 U128; * @param y denominator * @return x rounded down nearest multiple of y */ -#define RoundDown(x, y) ((x) / (y) * (y)) +#define RoundDownToMultiple(x, y) ((x) / (y) * (y)) #define Clamp(val, min, max) (((val) < (min)) ? (min) : ((val) > (max)) ? (max) : (val)) #ifdef __GNUC__ diff --git a/src/main.c b/src/main.c index 83ef671..1656626 100644 --- a/src/main.c +++ b/src/main.c @@ -5,14 +5,15 @@ void arena_dump(const Arena *arena) { + return; MemoryBlock *block = arena->current; puts("@@@@@@@@@@"); while (block) { puts("-----Arena-----"); printf("base: %p\n", block->base); printf("size: %u\n", block->size); - printf("pos: %u\n", block->pos); - printf("base_pos: %u\n", block->base_pos); + printf("first_free: %u\n", block->first_free); + printf("total_used: %lu\n",block->arena_offset); printf("prev: %p\n", block->prev); puts("-----Arena end-"); block = block->prev; @@ -20,8 +21,12 @@ void arena_dump(const Arena *arena) } static void arena_test_1(void) { - ArenaParams arena_params = {}; - arena_params.size = 1024+MemoryBlock_Header_Size; + TRACEF("\n"); + ArenaOptions arena_params = {}; + U32 arena_size = MB(1) + MemoryBlock_Header_Size; + U32 commit_size = KB(8); + arena_params.size = arena_size; + arena_params.commit_size = commit_size; Arena arena; arena_init(&arena, &arena_params); arena_dump(&arena); @@ -40,43 +45,78 @@ static void arena_test_1(void) arena_dump(&arena); arena_free(&arena); } + static void arena_test_2(void) { - ArenaParams arena_params = {}; - arena_params.size = 1024+MemoryBlock_Header_Size; + TRACEF("\n"); + + ArenaOptions arena_params = {}; + U32 arena_size = MB(1) + MemoryBlock_Header_Size; + U32 commit_size = KB(16); + U32 push_size = KB(32); + arena_params.size = arena_size; + arena_params.commit_size = commit_size; Arena arena; arena_init(&arena, &arena_params); void *ptr; - ptr = arena_push(&arena, 1024, 8); - MemoryZero(ptr, 1024); + ptr = arena_push(&arena, push_size, 8); + MemoryZero(ptr, push_size); printf("Allocated ptr %p\n", ptr); arena_dump(&arena); - ArenaCheckPoint checkpoint = arena_temp_begin(&arena); - ptr = arena_push(&arena, 4096, 8); - MemoryZero(ptr, 4096); + ArenaCheckpoint checkpoint = arena_temp_begin(&arena); + ptr = arena_push(&arena, MB(2), 8); + MemoryZero(ptr, MB(2)); - ptr = arena_push(&arena, 4096, 8); - MemoryZero(ptr, 4096); + ptr = arena_push(&arena, commit_size * 3, 8); + MemoryZero(ptr, commit_size * 3 ); + ptr = arena_push(&arena, commit_size , 8); + MemoryZero(ptr, commit_size ); - ArenaCheckPoint checkpoint2 = arena_temp_begin(&arena); + ArenaCheckpoint checkpoint2 = arena_temp_begin(&arena); ptr = arena_push(&arena, 64, 8); MemoryZero(ptr, 64); arena_temp_end(&checkpoint2); arena_temp_end(&checkpoint); - arena_pop_to(&arena, 1088); + arena_reset_to(&arena, 1088); arena_dump(&arena); - ptr = arena_push(&arena, 1, 1); + ptr = arena_push(&arena, 1, KB(16)); ((U8*)ptr)[0] = 0x37; - ptr = arena_push(&arena, 1, 1); + ptr = arena_push(&arena, 1, KB(16)); ((U8*)ptr)[0] = 0x73; - arena_pop_to(&arena, 1089); + ptr = arena_push(&arena, 0, KB(4)); + //((U8*)ptr)[0] = 0x73; + arena_reset_to(&arena, 1089); arena_dump(&arena); - arena_pop_to(&arena, 0); + arena_reset_to(&arena, 0); + ptr = arena_push(&arena, MB(1), 8); + MemoryZero(ptr, MB(1)); + arena_free(&arena); +} +static void arena_test_3(void) +{ + TRACEF("\n"); + + ArenaOptions arena_params = {}; + U32 arena_size = KB(97) + MemoryBlock_Header_Size; + U32 commit_size = KB(97); + arena_params.size = arena_size; + arena_params.commit_size = commit_size; + Arena arena; + arena_init(&arena, &arena_params); + arena_dump(&arena); + void *ptr = arena_push(&arena, KB(64), 32); + MemoryZero(ptr, KB(64)); + printf("Allocated ptr %p\n", ptr); + arena_dump(&arena); + ptr = arena_push(&arena, KB(97) - KB(64), 32); + MemoryZero(ptr, KB(97) - KB(64)); + printf("Allocated ptr %p\n", ptr); + arena_dump(&arena); arena_free(&arena); } @@ -84,4 +124,5 @@ int main(int argc, char *argv[]) { arena_test_1(); arena_test_2(); + arena_test_3(); } diff --git a/src/platform.c b/src/platform.c index aadb494..118c739 100644 --- a/src/platform.c +++ b/src/platform.c @@ -1,29 +1,16 @@ #include "base_core.h" +#include "platform.h" #include #include #include #include +#define IsPageAligned(x) (IsAlignedToPow2(x, platform_page_size())) + U32 platform_page_size(void) { - return getpagesize(); -} - -static INLINE size_t AlignToPageSize(size_t size) -{ - U32 page_size = platform_page_size(); - size_t aligned_size = AlignUpPow2(size, page_size); - - return aligned_size; -} - -static INLINE size_t AlignToPageStart(size_t size) -{ - U32 page_size = platform_page_size(); - size_t aligned_size = AlignDownPow2(size, page_size); - - return aligned_size; + return sysconf(_SC_PAGESIZE); } void *platform_malloc(size_t size) @@ -36,54 +23,67 @@ void platform_free(void *mem) free(mem); } - -void *memory_allocate(size_t size) +void *memory_allocate_and_commit(size_t size) { - if (size == 0) { - size = 1; + Assert(IsPageAligned(size)); + + void *mem = mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + TRACEF("memory_allocate size=%7zu mem=%p\n", size, mem); + if (mem == MAP_FAILED) { + mem = NULL; + } else { + int rc = madvise(mem, size, MADV_POPULATE_WRITE); + if (rc != 0) { + munmap(mem, size); + mem = NULL; + } } - size_t aligned_size = AlignToPageSize(size); - void *mem = mmap(0, aligned_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - TRACEF("memory_allocate size=%zu aligned_size=%zu mem=%p\n", size, aligned_size, mem); + return mem; +} + +void memory_free(void *mem, size_t size) +{ + TRACEF(" memory_free size=%7zu mem=%p\n", size, mem); + Assert(IsPageAligned((uintptr_t)mem)); + Assert(IsPageAligned(size)); + int rc = munmap(mem, size); + + Assert(rc == 0); + +} +void *memory_reserve(size_t size) +{ + Assert(IsPageAligned(size)); + void *mem = mmap(0, size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + TRACEF("memory_reserve size=%7zu mem=%p\n", size, mem); if (mem == MAP_FAILED) { mem = NULL; } return mem; } -void memory_free(void *mem, size_t size) -{ - size_t aligned_size = AlignToPageSize(size); - TRACEF(" memory_free size=%zu aligned_size=%zu mem=%p\n", size, aligned_size, mem); - - int rc = munmap(mem, aligned_size); - - Assert(rc == 0); -} - void memory_commit(void *mem, size_t size) { - size_t aligned_size = AlignToPageSize(size); - void *aligned_mem = (void *)AlignToPageStart((uintptr_t)mem); - TRACEF("memory_commit size=%zu aligned_size=%zu ptr=%p aligned_mem=%p\n", size, aligned_size, mem, aligned_mem); + Assert(IsPageAligned((uintptr_t)mem)); + Assert(IsPageAligned(size)); + TRACEF("memory_commit size=%7zu mem=%p\n", size, mem); int rc; - rc = mprotect(aligned_mem, aligned_size, PROT_READ | PROT_WRITE); + rc = mprotect(mem, size, PROT_READ | PROT_WRITE); Assert(rc == 0); - rc = madvise(aligned_mem, aligned_size, MADV_WILLNEED); + rc = madvise(mem, size, MADV_WILLNEED); Assert(rc == 0); } void memory_decommit(void *mem, size_t size) { - size_t aligned_size = AlignToPageSize(size); - void *aligned_mem = (void *)AlignToPageStart((uintptr_t)mem); - TRACEF("memory_commit size=%zu aligned_size=%zu ptr=%p aligned_mem=%p\n", size, aligned_size, mem, aligned_mem); - + TRACEF("memory_decommit size=%7zu mem=%p\n", size, mem); + Assert(IsPageAligned((uintptr_t)mem)); + Assert(IsPageAligned(size)); int rc; - rc = mprotect(aligned_mem, aligned_size, PROT_NONE); + rc = mprotect(mem, size, PROT_NONE); Assert(rc == 0); - rc = madvise(aligned_mem, aligned_size, MADV_DONTNEED); + rc = madvise(mem, size, MADV_DONTNEED); Assert(rc == 0); } diff --git a/src/platform.h b/src/platform.h index 574ca04..dcc28d6 100644 --- a/src/platform.h +++ b/src/platform.h @@ -2,9 +2,11 @@ #include void *platform_malloc(size_t size); -void platform_free(void *mem); U32 platform_page_size(void); -void *memory_allocate(size_t size); -void *memory_free(void *mem, size_t size); +void *memory_allocate_and_commit(size_t size); +void *memory_reserve(size_t size); +void memory_commit(void *mem, size_t size); +void memory_decommit(void *mem, size_t size); +void memory_free(void *mem, size_t size);