diff --git a/lib/Base/Allocator.h b/lib/Base/Allocator.h index ec60e5b..4099590 100644 --- a/lib/Base/Allocator.h +++ b/lib/Base/Allocator.h @@ -2,6 +2,16 @@ #include "Base.h" +#define ALLOCATOR_DEBUG_MODE 0 +#define ALLOCATOR_POISON_MEMORY_ON_ALLOCATION \ + (BUILD_DEBUG && ALLOCATOR_DEBUG_MODE) + +#if ALLOCATOR_POISON_MEMORY_ON_ALLOCATION + #define ALLOCATOR_INIT_VALUE 0xCD +#else + #define ALLOCATOR_INIT_VALUE 0xCD +#endif + enum class Allocator_Mode: s32 { ALLOCATE = 0, RESIZE = 1, diff --git a/lib/Base/Arena.cpp b/lib/Base/Arena.cpp index afe551f..de38361 100644 --- a/lib/Base/Arena.cpp +++ b/lib/Base/Arena.cpp @@ -1,8 +1,6 @@ #include "Arena.h" #include "Arena_Windows.cpp" -constexpr u16 ARENA_DEFAULT_ALIGNMENT = CPU_REGISTER_WIDTH_BYTES; - // For arrays, use `Array`, which is backed by the general purpose allocator // or use `ArenaArray` if you need to expand the size to an unknown size. void* arena_allocator_proc (Allocator_Mode mode, s64 requested_size, s64 old_size, void* old_memory, void* allocator_data) { @@ -13,7 +11,7 @@ void* arena_allocator_proc (Allocator_Mode mode, s64 requested_size, s64 old_siz return arena_alloc(arena, requested_size); } break; case Allocator_Mode::RESIZE: { - Assert(false); // DO NOT USE RESIZE WITH ARENAS! + Assert(false); // DO NOT USE RESIZE WITH ARENAS! :ArenaResizing // Or maybeeee... // Resize should check if current_point matches the end of the old allocation? // and resize accordingly + pass back same pointer. @@ -102,12 +100,15 @@ void arena_set_bootstrap_flag (Arena* arena) { arena->flags |= Arena_Flags::Is_B void arena_reset_keeping_memory (Arena* arena) { if (!is_valid(arena)) return; + arena->current_point = arena_start(arena); } void arena_reset (Arena* arena) { if (!is_valid(arena)) return; + arena->current_point = arena_start(arena); + free_pages_down_to(arena, arena->initial_commit_page_count); } @@ -116,6 +117,8 @@ void arena_reset_overwriting_memory (Arena* arena, Memory_Wipe_Function wipe_fun Assert(wipe_function != nullptr); if (wipe_function == nullptr) return; wipe_function(arena_start(arena), (u64)(arena->current_point - arena_start(arena))); + + arena_reset(arena); } void* arena_alloc (Arena* arena, s64 byte_count) { @@ -174,10 +177,30 @@ s64 reserve_size (Arena_Reserve ar) { return 0; } +constexpr s64 Arena_Sizes[6] = { + 64LL * 1024, + 2LL * 1024 * 1024, + 64LL * 1024 * 1024, + 2LL * 1024 * 1024 * 1024, + 64LL * 1024 * 1024 * 1024, + 2LL * 1024 * 1024 * 1024 * 1024, +}; + +Arena_Reserve next_reserve_size (s64 size) { + for (u8 i = 0; i < 6; i += 1) { + if (size <= Arena_Sizes[i]) { + return (Arena_Reserve)i; + } + } + + return Arena_Reserve::Size_64T; +} + +// arena_usage_bytes is kinda pointless tbh. s64 arena_usage_bytes (Arena* arena) { return (s64)(arena->current_point - arena_start(arena)); } s64 arena_usage_committed_bytes (Arena* arena) { return (s64)(arena->first_uncommitted_page - arena->memory_base); } // for arena details, I need to setup my string builder first. -Allocator get_allocator(Arena* arena) { +Allocator get_allocator (Arena* arena) { return { arena_allocator_proc, arena }; } diff --git a/lib/Base/Arena.h b/lib/Base/Arena.h index f5b0888..4be8ef9 100644 --- a/lib/Base/Arena.h +++ b/lib/Base/Arena.h @@ -1,10 +1,14 @@ #pragma once +struct ExpandableArena; // fwd declare #temp + #if OS_WINDOWS constexpr u32 ARENA_DEFAULT_COMMIT_PAGE_COUNT = 16; // 16 * 4k page = 64kB constexpr s64 ARENA_DEFAULT_COMMIT_SIZE_BYTES = 65536; #endif +constexpr u16 ARENA_DEFAULT_ALIGNMENT = CPU_REGISTER_WIDTH_BYTES; + #define ARENA_DEBUG BUILD_DEBUG enum class Arena_Reserve: u8 { @@ -87,7 +91,7 @@ s64 arena_usage_committed_bytes (Arena* arena); s64 reserve_size (Arena* arena); s64 reserve_size (Arena_Reserve ar); bool is_valid (Arena* arena); -Allocator get_allocator(Arena* arena); +Allocator get_allocator (Arena* arena); // Platform-Specific Implementations (forward-declared) void platform_init (Arena* arena, s64 new_reserve); @@ -95,14 +99,20 @@ void extend_committed_pages (Arena* arena, u8* end); void free_pages_down_to (Arena* arena, s64 pages_to_keep); void arena_delete (Arena* arena); +Arena_Reserve next_reserve_size (s64 size); + struct Auto_Reset { Arena* arena; u8* starting_point; Auto_Reset(Arena* arena) { + Assert(is_valid(arena)); this->arena = arena; this->starting_point = arena->current_point; - Assert(is_valid(arena)); + } + + Auto_Reset(ExpandableArena* arena_ex) { + Auto_Reset((Arena*)arena_ex); } ~Auto_Reset() { @@ -137,6 +147,10 @@ struct Push_Alignment { // #rename to Arena_Push_Alignment? this->arena->alignment = alignment; } + Push_Alignment(ExpandableArena* arena_ex, u16 alignment) { + Push_Alignment((Arena*)arena_ex, alignment); + } + ~Push_Alignment() { arena->alignment = original_alignment; } diff --git a/lib/Base/Arena_Table.cpp b/lib/Base/Arena_Table.cpp index 60b7c72..23e5dc0 100644 --- a/lib/Base/Arena_Table.cpp +++ b/lib/Base/Arena_Table.cpp @@ -1,6 +1,7 @@ // API in Arena.h #include +global b64 arena_tables_initialized = false; global std::mutex arena_table_mutex; global s32 arenas_in_flight_count[6]; global Array arena_free_table[6]; @@ -15,6 +16,8 @@ void initialize_arena_table () { array_reserve(arena_free_table[i], 64); array_reserve(arenas_in_flight[i], 64); } + + arena_tables_initialized = true; } Arena* next_arena (Arena_Reserve reserve_size) { diff --git a/lib/Base/Arena_Windows.cpp b/lib/Base/Arena_Windows.cpp index 859050e..ed0da42 100644 --- a/lib/Base/Arena_Windows.cpp +++ b/lib/Base/Arena_Windows.cpp @@ -32,7 +32,7 @@ void extend_committed_pages (Arena* arena, u8* end) { void free_pages_down_to (Arena* arena, s64 pages_to_keep) { if (arena == nullptr) return; - Assert(pages_to_keep >= 0); + Assert(pages_to_keep >= 1); // Always keep one page because we bootstrap a lot. s64 bytes_to_keep = pages_to_keep * PLATFORM_MEMORY_PAGE_SIZE; if (bytes_to_keep >= reserve_size(arena)) { Assert(false); // Break in debug builds, but release we just do nothing. diff --git a/lib/Base/Base.h b/lib/Base/Base.h index a1c76e9..38e634b 100644 --- a/lib/Base/Base.h +++ b/lib/Base/Base.h @@ -178,12 +178,16 @@ force_inline s64 Next_Power_Of_Two(s64 v) { // usage `Auto_Reset guard(arena);` within a scope. #define auto_reset(x) \ Auto_Reset Concat(_auto_reset_guard_, __LINE__)(x) -#define push_allocator(x) Push_Allocator Concat(_push_alloc_guard_, __LINE__)(x) +#define push_allocator(x) \ + Push_Allocator Concat(_push_alloc_guard_, __LINE__)(x) #define push_alignment(x, y) \ Push_Alignment Concat(_push_align_guard_, __LINE__)(x, y) #define push_arena(x) \ - Push_Arena Concat(_push_alloc_guard_, __LINE__)(x) -#define auto_release_temp() auto_release(get_temp_allocator()); + Push_Arena Concat(_push_arena_guard_, __LINE__)(x) +#define push_expandable_arena(x) \ + Push_Expandable_Arena Concat(_push_ex_arena_guard_, __LINE__)(x) +#define auto_release_temp() \ + auto_release(get_temp_allocator()); #define auto_release(x) \ Auto_Release Concat(_auto_release_guard_, __LINE__)(x) diff --git a/lib/Base/Base_Thread_Context.cpp b/lib/Base/Base_Thread_Context.cpp index f7878bc..52dbeeb 100644 --- a/lib/Base/Base_Thread_Context.cpp +++ b/lib/Base/Base_Thread_Context.cpp @@ -1,22 +1,4 @@ // See Context_Base in jai, and TCTX in raddebugger: -struct Thread_Context { - Arena* temp; // Used for temporary allocations, scratch space. - Arena* arena; // general purpose local arena - - Allocator allocator; - s32 thread_idx; - u16 _padding0; - u16 GPAllocator_alignment = 16; - // Logger logger; - // Stack_Trace* stack_trace; - // #TODO: other debug information - // #TODO: - // Array threads_created; // maybe should be linked-list? - // Thread* thread_that_created_me = nullptr; // so we can remove from above array - // Mutex thread_context_mutex; - - string thread_name; -}; Thread_Context* get_thread_context(); @@ -24,6 +6,14 @@ struct Push_Arena { Thread_Context* context; Allocator original_allocator; + Push_Arena(ExpandableArena* arena_ex) { + Assert(is_valid(arena_ex)); + context = get_thread_context(); + Assert(context != nullptr); + original_allocator = context->allocator; + context->allocator = get_allocator(arena_ex); + } + Push_Arena(Arena* arena) { Assert(is_valid(arena)); context = get_thread_context(); @@ -76,7 +66,7 @@ force_inline void set_thread_context (Thread_Context* new_context) { thread_local_context = new_context; } -Thread_Context* get_thread_context() { +Thread_Context* get_thread_context () { return (Thread_Context*)thread_local_context; } @@ -91,10 +81,10 @@ force_inline Allocator get_context_allocator() { void temp_reset_keeping_memory() { Thread_Context* context = get_thread_context(); - arena_reset_keeping_memory(context->temp); + arena_reset(context->temp, false); } void temp_reset() { Thread_Context* context = get_thread_context(); - arena_reset(context->temp); + arena_reset(context->temp, true); } \ No newline at end of file diff --git a/lib/Base/Base_Thread_Context.h b/lib/Base/Base_Thread_Context.h new file mode 100644 index 0000000..df5d36e --- /dev/null +++ b/lib/Base/Base_Thread_Context.h @@ -0,0 +1,20 @@ +struct Thread_Context { + ExpandableArena* temp; // Used for temporary allocations, scratch space. + ExpandableArena* arena; // general purpose local arena + + Allocator allocator; + s32 thread_idx; + u16 _padding0; + u16 GPAllocator_alignment = 16; + // Logger logger; + // Stack_Trace* stack_trace; + // #TODO: other debug information + // #TODO: + // Array threads_created; // maybe should be linked-list? + // Thread* thread_that_created_me = nullptr; // so we can remove from above array + // Mutex thread_context_mutex; + + string thread_name; +}; + +Thread_Context* get_thread_context (); \ No newline at end of file diff --git a/lib/Base/Expandable_Arena.cpp b/lib/Base/Expandable_Arena.cpp new file mode 100644 index 0000000..cd2ef89 --- /dev/null +++ b/lib/Base/Expandable_Arena.cpp @@ -0,0 +1,115 @@ +ExpandableArena* expandable_arena_new (Arena_Reserve starting_reserve, s32 commit_page_count) { + ExpandableArena* new_arena = (ExpandableArena*)bootstrap_arena(starting_reserve, commit_page_count); + // Note: beyond first 32 bytes ExpandableArena will not be initialized, so we do it here: + new_arena->current = (Arena*)new_arena; + + new_arena->current_point = expandable_arena_start(new_arena); + new_arena->next_arenas = Array(); // next_arenas will be uninitialized, so we have to do this + new_arena->next_arenas.allocator = get_allocator(new_arena); + + array_reserve(new_arena->next_arenas, 8); + + return new_arena; +} + +void* expandable_arena_allocator_proc (Allocator_Mode mode, s64 requested_size, s64 old_size, void* old_memory, void* allocator_data) { + ExpandableArena* arena = (ExpandableArena*)allocator_data; + Assert(arena != nullptr); + switch (mode) { + case Allocator_Mode::ALLOCATE: { + return expandable_arena_alloc(arena, requested_size); + } break; + case Allocator_Mode::RESIZE: { + // See note :ArenaResizing + void* new_memory = expandable_arena_alloc(arena, requested_size); + memcpy(new_memory, old_memory, old_size); + return new_memory; + } break; + case Allocator_Mode::DEALLOCATE: + return nullptr; + break; + } + + return nullptr; +} + +bool is_valid (ExpandableArena* arena) { + return (arena != nullptr) + && (arena->memory_base != nullptr) + && (arena->current != nullptr); +} + +void* expandable_arena_alloc (ExpandableArena* arena_ex, s64 byte_count) { + Assert(arena_ex != nullptr); + Assert(arena_ex->memory_base != nullptr); // must be initialized before calling. + Assert(is_valid(arena_ex)); + Assert(arena_tables_initialized); + + Arena* arena = (Arena*)arena_ex->current; + + u8* result = Align(arena->current_point, arena->alignment); + u8* result_end = result + byte_count; + + if (result_end > arena->first_uncommitted_page) { + if (result_end > arena_address_limit(arena)) { + // Pick an appropriate reserve size that will fit this allocation. + Arena_Reserve new_min_reserve = next_reserve_size(byte_count); + if (arena->reserve_size > new_min_reserve) { + new_min_reserve = arena->reserve_size; + } + + Arena* new_arena = next_arena(new_min_reserve); + + new_arena->alignment = arena_ex->alignment; + new_arena->flags = arena_ex->flags; + + arena_ex->current = new_arena; + array_add(arena_ex->next_arenas, new_arena); + + // Get to actual allocation: + result = Align(new_arena->current_point, new_arena->alignment); + result_end = result + byte_count; + + if (result_end > arena_address_limit(new_arena)) { + extend_committed_pages(new_arena, result_end); + } + } else { + extend_committed_pages(arena, result_end); + } + } + + arena_ex->current->current_point = result_end; + + return result; +} + +u8* expandable_arena_start (ExpandableArena* arena_ex) { + return Align(arena_ex->memory_base + sizeof(ExpandableArena), ARENA_DEFAULT_ALIGNMENT); +} + +Allocator get_allocator (ExpandableArena* arena_ex) { + return { expandable_arena_allocator_proc, arena_ex }; +} + +void arena_reset (ExpandableArena* arena_ex, bool free_extra_pages) { + if (!is_valid(arena_ex)) return; + + // Free arenas in `next_arenas` + for (s64 i = 0; i < arena_ex->next_arenas.count; i += 1) { + release_arena(arena_ex->next_arenas[i], free_extra_pages); + } + + // Reset next_arenas + array_reset_keeping_memory(arena_ex->next_arenas); + + arena_ex->current_point = expandable_arena_start(arena_ex); + + if (free_extra_pages) { + free_pages_down_to((Arena*)arena_ex, arena_ex->initial_commit_page_count); + } +} + +force_inline void arena_delete (ExpandableArena* arena_ex) { + arena_reset(arena_ex, true); + arena_delete((Arena*)arena_ex); +} diff --git a/lib/Base/Expandable_Arena.h b/lib/Base/Expandable_Arena.h new file mode 100644 index 0000000..469cd82 --- /dev/null +++ b/lib/Base/Expandable_Arena.h @@ -0,0 +1,26 @@ +// Just an idea I had so that we can start with a small arena and increase on an as-needed basis, +// this way allocations are always extremely fast. + +// Note that this downcasts to Arena, so can be initialized in the same way. +// DO NOT USE push_arena WITH THIS! IT WILL CALL THE WRONG PROC! +struct ExpandableArena { + u8* current_point = nullptr; + u8* memory_base = nullptr; + u8* first_uncommitted_page = nullptr; + u16 alignment = CPU_REGISTER_WIDTH_BYTES; + Arena_Reserve reserve_size = Arena_Reserve::Size_64K; + Arena_Flags flags = Arena_Flags::None; + u32 initial_commit_page_count = ARENA_DEFAULT_COMMIT_PAGE_COUNT; + // 32 bytes up to here + Arena* current; + Array next_arenas; // 40 B +}; + +ExpandableArena* expandable_arena_new (Arena_Reserve starting_reserve=Arena_Reserve::Size_64K, s32 commit_page_count=8); +void* expandable_arena_allocator_proc (Allocator_Mode mode, s64 requested_size, s64 old_size, void* old_memory, void* allocator_data); +bool is_valid (ExpandableArena* arena); +void* expandable_arena_alloc (ExpandableArena* arena_ex, s64 byte_count); +u8* expandable_arena_start (ExpandableArena* arena_ex); +Allocator get_allocator (ExpandableArena* arena_ex); +void arena_reset (ExpandableArena* arena_ex, bool free_extra_pages=true); +force_inline void arena_delete (ExpandableArena* arena_ex); diff --git a/lib/Base/General_Purpose_Allocator.cpp b/lib/Base/General_Purpose_Allocator.cpp index 03df8eb..da5e647 100644 --- a/lib/Base/General_Purpose_Allocator.cpp +++ b/lib/Base/General_Purpose_Allocator.cpp @@ -101,7 +101,7 @@ void* GPAllocator_New (s64 new_size, s64 alignment, bool initialize) { auto memory = Aligned_Alloc(new_size, alignment); // _aligned_malloc does not zero memory, so we can zero it here - if (initialize && memory) { memset(memory, 0, new_size); } + if (initialize && memory) { memset(memory, ALLOCATOR_INIT_VALUE, new_size); } Add_Allocation(new_size, memory, (s32)alignment); return memory; } @@ -117,7 +117,7 @@ void* GPAllocator_Resize (s64 old_size, void* old_memory, s64 new_size, s64 alig auto new_memory_address = Aligned_Realloc(old_size, old_memory, new_size, alignment); if (initialize && new_memory_address && new_size > old_size) { - memset((u8*)new_memory_address + old_size, 0, new_size - old_size); + memset((u8*)new_memory_address + old_size, ALLOCATOR_INIT_VALUE, new_size - old_size); } Remove_Allocation(old_memory); Add_Allocation(new_size, new_memory_address, (s32)alignment); diff --git a/lib/OS/Base_Entry_Point.cpp b/lib/OS/Base_Entry_Point.cpp index c03c251..1f65c24 100644 --- a/lib/OS/Base_Entry_Point.cpp +++ b/lib/OS/Base_Entry_Point.cpp @@ -4,11 +4,12 @@ internal void Bootstrap_Main_Thread_Context () { // 1. Setup arena table initialize_arena_table(); // 2. Setup thread local context - Arena* arena = next_arena(Arena_Reserve::Size_64G); - thread_local_context = New(get_allocator(arena)); - thread_local_context->temp = next_arena(Arena_Reserve::Size_64G); - thread_local_context->arena = arena; - thread_local_context->allocator = get_allocator(arena); + ExpandableArena* arena_ex = expandable_arena_new(Arena_Reserve::Size_64M, 16); + + thread_local_context = New(get_allocator(arena_ex)); + thread_local_context->temp = expandable_arena_new(Arena_Reserve::Size_2M, 16); + thread_local_context->arena = arena_ex; + thread_local_context->allocator = get_allocator(arena_ex); thread_local_context->thread_idx = 0; thread_local_context->thread_name = "Main Thread"; // thread_local_context->logger = init_logger(); @@ -38,9 +39,11 @@ void run_arena_array_tests () { internal void Main_Entry_Point (int argc, WCHAR **argv) { run_arena_array_tests(); - Worker_Info* info = (Worker_Info*)GPAllocator_New(sizeof(Worker_Info), 64); + // Worker_Info* info = (Worker_Info*)GPAllocator_New(sizeof(Worker_Info), 64); debug_break(); + printf("sizeof(Array): %zd\n", sizeof(Array)); + printf("sizeof(Arena): %zd\n", sizeof(Arena)); printf("sizeof(Worker_Info): %zd\n", sizeof(Thread)); printf("sizeof(Worker_Info): %zd\n", sizeof(Worker_Info)); diff --git a/lib/OS/OS_Win32.cpp b/lib/OS/OS_Win32.cpp index cf18e2e..4544e7f 100644 --- a/lib/OS/OS_Win32.cpp +++ b/lib/OS/OS_Win32.cpp @@ -202,16 +202,14 @@ internal bool thread_init (Thread* thread, Thread_Proc proc, string thread_name= s64 this_thread_index = InterlockedIncrement(&next_thread_index); - // We may not always want such a bulky thread startup. The - // size of the starting arena and temp should be parameterized (+2 bytes) - // make thread_init_ex with params... - Arena* arena = next_arena(Arena_Reserve::Size_64G); - push_arena(arena); - thread->context = New(get_allocator(arena)); - thread->context->temp = next_arena(Arena_Reserve::Size_64G); - thread->context->arena = arena; - thread->context->allocator = get_allocator(arena); + ExpandableArena* arena_ex = expandable_arena_new(Arena_Reserve::Size_64M, 16); + + thread->context = New(get_allocator(arena_ex)); + thread->context->temp = expandable_arena_new(Arena_Reserve::Size_2M, 16); + thread->context->arena = arena_ex; + thread->context->allocator = get_allocator(arena_ex); thread->context->thread_idx = (s32)this_thread_index; + push_arena(arena_ex); thread->context->thread_name = copy_string(thread_name); thread->os_thread.windows_thread = windows_thread; @@ -230,8 +228,8 @@ internal void thread_deinit (Thread* thread) { thread->os_thread.windows_thread = nullptr; // #TODO: Thread cleanup: - release_arena(thread->context->temp, true); - release_arena(thread->context->arena, true); + arena_delete(thread->context->temp); + arena_delete(thread->context->arena); } internal void thread_start (Thread* thread) { diff --git a/lib_main.cpp b/lib_main.cpp index aeb5488..9da8f47 100644 --- a/lib_main.cpp +++ b/lib_main.cpp @@ -9,16 +9,19 @@ #include "lib/Base/Base.h" #include "lib/Base/Allocator.h" #include "lib/Base/Array.h" +#include "lib/Base/String.cpp" #include "lib/Base/General_Purpose_Allocator.h" #include "lib/Base/Arena.h" #include "lib/Base/Arena_Array.h" +#include "lib/Base/Arena_Table.cpp" +#include "lib/Base/Base_Thread_Context.h" +#include "lib/Base/Expandable_Arena.h" -#include "lib/Base/String.cpp" #include "lib/Base/ErrorCodes.cpp" #include "lib/Base/Arena.cpp" -#include "lib/Base/Arena_Table.cpp" #include "lib/Base/Base_Thread_Context.cpp" +#include "lib/Base/Expandable_Arena.cpp" #include "lib/Base/Allocator.cpp" #include "lib/Base/General_Purpose_Allocator.cpp" #include "lib/Base/Basic.cpp"