#import "std.sx"; // --- Allocator protocol --- Allocator :: struct { ctx: *void; alloc: (*void, s64) -> *void; free: (*void, *void) -> void; } // --- GPA: general purpose allocator (malloc/free wrapper) --- GPA :: struct { alloc_count: s64; } gpa_alloc :: (ctx: *void, size: s64) -> *void { gpa : *GPA = xx ctx; gpa.alloc_count += 1; malloc(size); } gpa_free :: (ctx: *void, ptr: *void) { gpa : *GPA = xx ctx; gpa.alloc_count -= 1; free(ptr); } gpa_create :: (gpa: *GPA) -> Allocator { ctx : *void = xx gpa; Allocator.{ ctx = ctx, alloc = gpa_alloc, free = gpa_free }; } // --- Arena: multi-chunk bump allocator (Zig-inspired) --- ArenaChunk :: struct { next: *void; // *ArenaChunk cap: s64; // total chunk size including this header } Arena :: struct { first: *void; // *ArenaChunk — head of list (newest first) end_index: s64; // bump position within current chunk's usable area parent: Allocator; // backing allocator } arena_add_chunk :: (a: *Arena, min_size: s64) { first_i : s64 = xx a.first; prev_cap := if first_i != 0 then { c : *ArenaChunk = xx a.first; c.cap; } else 0; needed := min_size + 16 + 16; len := (prev_cap + needed) * 3 / 2; raw := a.parent.alloc(a.parent.ctx, len); chunk : *ArenaChunk = xx raw; chunk.next = a.first; chunk.cap = len; a.first = xx chunk; a.end_index = 0; } arena_alloc :: (ctx: *void, size: s64) -> *void { a : *Arena = xx ctx; aligned := (size + 7) & (0 - 8); first_i : s64 = xx a.first; if first_i != 0 { chunk : *ArenaChunk = xx a.first; usable := chunk.cap - 16; if a.end_index + aligned <= usable { buf : [*]u8 = xx a.first; ptr : *void = xx @buf[16 + a.end_index]; a.end_index = a.end_index + aligned; return ptr; } } arena_add_chunk(a, aligned); buf : [*]u8 = xx a.first; ptr : *void = xx @buf[16 + a.end_index]; a.end_index = a.end_index + aligned; ptr; } arena_free :: (ctx: *void, ptr: *void) { } arena_create :: (a: *Arena, parent: Allocator, size: s64) -> Allocator { a.first = null; a.end_index = 0; a.parent = parent; arena_add_chunk(a, size); ctx : *void = xx a; Allocator.{ ctx = ctx, alloc = arena_alloc, free = arena_free }; } arena_reset :: (a: *Arena) { // Keep first chunk (newest/largest), free the rest first_i : s64 = xx a.first; if first_i != 0 { chunk : *ArenaChunk = xx a.first; it : s64 = xx chunk.next; while it != 0 { c : *ArenaChunk = xx it; next_i : s64 = xx c.next; a.parent.free(a.parent.ctx, xx c); it = next_i; } chunk.next = null; } a.end_index = 0; } arena_deinit :: (a: *Arena) { it : s64 = xx a.first; while it != 0 { c : *ArenaChunk = xx it; next_i : s64 = xx c.next; a.parent.free(a.parent.ctx, xx c); it = next_i; } a.first = null; a.end_index = 0; } // --- BufAlloc: bump allocator backed by a user-provided slice --- BufAlloc :: struct { buf: [*]u8; len: s64; pos: s64; } buf_alloc :: (ctx: *void, size: s64) -> *void { b : *BufAlloc = xx ctx; aligned := (size + 7) & (0 - 8); if b.pos + aligned > b.len { return null; } ptr : *void = xx @b.buf[b.pos]; b.pos = b.pos + aligned; ptr; } buf_free :: (ctx: *void, ptr: *void) { } buf_create :: (b: *BufAlloc, buf: [*]u8, len: s64) -> Allocator { b.buf = buf; b.len = len; b.pos = 0; ctx : *void = xx b; Allocator.{ ctx = ctx, alloc = buf_alloc, free = buf_free }; } buf_reset :: (b: *BufAlloc) { b.pos = 0; }