// Copyright 2022 Alexey Kutepov // 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. #ifndef ARENA_H_ #define ARENA_H_ #include #include #ifndef ARENA_ASSERT #include #define ARENA_ASSERT assert #endif #define ARENA_BACKEND_LIBC_MALLOC 0 #define ARENA_BACKEND_LINUX_MMAP 1 #define ARENA_BACKEND_WIN32_VIRTUALALLOC 2 #define ARENA_BACKEND_WASM_HEAPBASE 3 #ifndef ARENA_BACKEND #define ARENA_BACKEND ARENA_BACKEND_LIBC_MALLOC #endif // ARENA_BACKEND typedef struct Region Region; struct Region { Region *next; size_t count; size_t capacity; uintptr_t data[]; }; typedef struct { Region *begin, *end; } Arena; #define REGION_DEFAULT_CAPACITY (8*1024) Region *new_region(size_t capacity); void free_region(Region *r); // TODO: snapshot/rewind capability for the arena // - Snapshot should be combination of a->end and a->end->count. // - Rewinding should be restoring a->end and a->end->count from the snapshot and // setting count-s of all the Region-s after the remembered a->end to 0. void *arena_alloc(Arena *a, size_t size_bytes); void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz); void arena_reset(Arena *a); void arena_free(Arena *a); #endif // ARENA_H_ #ifdef ARENA_IMPLEMENTATION #if ARENA_BACKEND == ARENA_BACKEND_LIBC_MALLOC #include // TODO: instead of accepting specific capacity new_region() should accept the size of the object we want to fit into the region // It should be up to new_region() to decide the actual capacity to allocate Region *new_region(size_t capacity) { size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity; // TODO: it would be nice if we could guarantee that the regions are allocated by ARENA_BACKEND_LIBC_MALLOC are page aligned Region *r = malloc(size_bytes); ARENA_ASSERT(r); r->next = NULL; r->count = 0; r->capacity = capacity; return r; } void free_region(Region *r) { free(r); } #elif ARENA_BACKEND == ARENA_BACKEND_LINUX_MMAP #include #include Region *new_region(size_t capacity) { size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity; Region *r = mmap(NULL, size_bytes, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); ARENA_ASSERT(r != MAP_FAILED); r->next = NULL; r->count = 0; r->capacity = capacity; return r; } void free_region(Region *r) { size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * r->capacity; int ret = munmap(r, size_bytes); ARENA_ASSERT(ret == 0); } #elif ARENA_BACKEND == ARENA_BACKEND_WIN32_VIRTUALALLOC #if !defined(_WIN32) # error "Current platform is not Windows" #endif #define WIN32_LEAN_AND_MEAN #include #define INV_HANDLE(x) (((x) == NULL) || ((x) == INVALID_HANDLE_VALUE)) Region *new_region(size_t capacity) { SIZE_T size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity; Region *r = VirtualAllocEx( GetCurrentProcess(), /* Allocate in current process address space */ NULL, /* Unknown position */ size_bytes, /* Bytes to allocate */ MEM_COMMIT | MEM_RESERVE, /* Reserve and commit allocated page */ PAGE_READWRITE /* Permissions ( Read/Write )*/ ); if (INV_HANDLE(r)) ARENA_ASSERT(0 && "VirtualAllocEx() failed."); r->next = NULL; r->count = 0; r->capacity = capacity; return r; } void free_region(Region *r) { if (INV_HANDLE(r)) return; BOOL free_result = VirtualFreeEx( GetCurrentProcess(), /* Deallocate from current process address space */ (LPVOID)r, /* Address to deallocate */ 0, /* Bytes to deallocate ( Unknown, deallocate entire page ) */ MEM_RELEASE /* Release the page ( And implicitly decommit it ) */ ); if (FALSE == free_result) ARENA_ASSERT(0 && "VirtualFreeEx() failed."); } #elif ARENA_BACKEND == ARENA_BACKEND_WASM_HEAPBASE # error "TODO: WASM __heap_base backend is not implemented yet" #else # error "Unknown Arena backend" #endif // TODO: add debug statistic collection mode for arena // Should collect things like: // - How many times new_region was called // - How many times existing region was skipped // - How many times allocation exceeded REGION_DEFAULT_CAPACITY void *arena_alloc(Arena *a, size_t size_bytes) { size_t size = (size_bytes + sizeof(uintptr_t) - 1)/sizeof(uintptr_t); if (a->end == NULL) { ARENA_ASSERT(a->begin == NULL); size_t capacity = REGION_DEFAULT_CAPACITY; if (capacity < size) capacity = size; a->end = new_region(capacity); a->begin = a->end; } while (a->end->count + size > a->end->capacity && a->end->next != NULL) { a->end = a->end->next; } if (a->end->count + size > a->end->capacity) { ARENA_ASSERT(a->end->next == NULL); size_t capacity = REGION_DEFAULT_CAPACITY; if (capacity < size) capacity = size; a->end->next = new_region(capacity); a->end = a->end->next; } void *result = &a->end->data[a->end->count]; a->end->count += size; return result; } void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz) { if (newsz <= oldsz) return oldptr; void *newptr = arena_alloc(a, newsz); char *newptr_char = newptr; char *oldptr_char = oldptr; for (size_t i = 0; i < oldsz; ++i) { newptr_char[i] = oldptr_char[i]; } return newptr; } void arena_reset(Arena *a) { for (Region *r = a->begin; r != NULL; r = r->next) { r->count = 0; } a->end = a->begin; } void arena_free(Arena *a) { Region *r = a->begin; while (r) { Region *r0 = r; r = r->next; free_region(r0); } a->begin = NULL; a->end = NULL; } #endif // ARENA_IMPLEMENTATION