/Users/alexjokela/projects/lattice/src/memory.c
Line | Count | Source |
1 | | #include "memory.h" |
2 | | #include <stdlib.h> |
3 | | #include <string.h> |
4 | | |
5 | | /* ── Fluid Heap ── */ |
6 | | |
7 | 913 | FluidHeap *fluid_heap_new(void) { |
8 | 913 | FluidHeap *h = calloc(1, sizeof(FluidHeap)); |
9 | 913 | h->gc_threshold = 1024 * 1024; /* 1 MB default */ |
10 | 913 | return h; |
11 | 913 | } |
12 | | |
13 | 913 | void fluid_heap_free(FluidHeap *h) { |
14 | 913 | if (!h) return; |
15 | 913 | FluidAlloc *a = h->allocs; |
16 | 1.10k | while (a) { |
17 | 194 | FluidAlloc *next = a->next; |
18 | 194 | free(a->ptr); |
19 | 194 | free(a); |
20 | 194 | a = next; |
21 | 194 | } |
22 | 913 | free(h); |
23 | 913 | } |
24 | | |
25 | 57.5k | void *fluid_alloc(FluidHeap *h, size_t size) { |
26 | 57.5k | void *ptr = malloc(size); |
27 | 57.5k | FluidAlloc *a = malloc(sizeof(FluidAlloc)); |
28 | 57.5k | a->ptr = ptr; |
29 | 57.5k | a->size = size; |
30 | 57.5k | a->marked = false; |
31 | 57.5k | a->next = h->allocs; |
32 | 57.5k | h->allocs = a; |
33 | 57.5k | h->total_bytes += size; |
34 | 57.5k | h->alloc_count++; |
35 | 57.5k | h->cumulative_bytes += size; |
36 | 57.5k | if (h->total_bytes > h->peak_bytes) |
37 | 27.1k | h->peak_bytes = h->total_bytes; |
38 | 57.5k | return ptr; |
39 | 57.5k | } |
40 | | |
41 | 58.1k | bool fluid_dealloc(FluidHeap *h, void *ptr) { |
42 | 58.1k | FluidAlloc **prev = &h->allocs; |
43 | 6.98M | for (FluidAlloc *a = h->allocs; a; a = a->next) { |
44 | 6.98M | if (a->ptr == ptr) { |
45 | 57.3k | *prev = a->next; |
46 | 57.3k | h->total_bytes -= a->size; |
47 | 57.3k | h->alloc_count--; |
48 | 57.3k | free(a->ptr); |
49 | 57.3k | free(a); |
50 | 57.3k | return true; |
51 | 57.3k | } |
52 | 6.92M | prev = &a->next; |
53 | 6.92M | } |
54 | 812 | return false; |
55 | 58.1k | } |
56 | | |
57 | 63 | size_t fluid_live_count(const FluidHeap *h) { |
58 | 63 | return h->alloc_count; |
59 | 63 | } |
60 | | |
61 | 48 | size_t fluid_total_bytes(const FluidHeap *h) { |
62 | 48 | return h->total_bytes; |
63 | 48 | } |
64 | | |
65 | 16.1k | bool fluid_mark(FluidHeap *h, void *ptr) { |
66 | 44.9k | for (FluidAlloc *a = h->allocs; a; a = a->next) { |
67 | 44.8k | if (a->ptr == ptr) { |
68 | 16.0k | a->marked = true; |
69 | 16.0k | return true; |
70 | 16.0k | } |
71 | 44.8k | } |
72 | 103 | return false; |
73 | 16.1k | } |
74 | | |
75 | 7.38k | void fluid_unmark_all(FluidHeap *h) { |
76 | 22.9k | for (FluidAlloc *a = h->allocs; a; a = a->next) { |
77 | 15.5k | a->marked = false; |
78 | 15.5k | } |
79 | 7.38k | } |
80 | | |
81 | 7.38k | size_t fluid_sweep(FluidHeap *h) { |
82 | 7.38k | size_t freed = 0; |
83 | 7.38k | FluidAlloc **prev = &h->allocs; |
84 | 7.38k | FluidAlloc *a = h->allocs; |
85 | 22.9k | while (a) { |
86 | 15.5k | if (!a->marked) { |
87 | 12 | *prev = a->next; |
88 | 12 | h->total_bytes -= a->size; |
89 | 12 | h->alloc_count--; |
90 | 12 | FluidAlloc *next = a->next; |
91 | 12 | free(a->ptr); |
92 | 12 | free(a); |
93 | 12 | a = next; |
94 | 12 | freed++; |
95 | 15.5k | } else { |
96 | 15.5k | prev = &a->next; |
97 | 15.5k | a = a->next; |
98 | 15.5k | } |
99 | 15.5k | } |
100 | 7.38k | return freed; |
101 | 7.38k | } |
102 | | |
103 | | /* ── Arena Pages ── */ |
104 | | |
105 | 2.15k | static ArenaPage *arena_page_new(size_t cap) { |
106 | 2.15k | ArenaPage *p = malloc(sizeof(ArenaPage)); |
107 | 2.15k | p->data = malloc(cap); |
108 | 2.15k | p->used = 0; |
109 | 2.15k | p->cap = cap; |
110 | 2.15k | p->next = NULL; |
111 | 2.15k | return p; |
112 | 2.15k | } |
113 | | |
114 | 2.12k | static void arena_page_free_list(ArenaPage *p) { |
115 | 4.28k | while (p) { |
116 | 2.15k | ArenaPage *next = p->next; |
117 | 2.15k | free(p->data); |
118 | 2.15k | free(p); |
119 | 2.15k | p = next; |
120 | 2.15k | } |
121 | 2.12k | } |
122 | | |
123 | | /* ── Bump Arena ── */ |
124 | | |
125 | 1.70k | BumpArena *bump_arena_new(void) { |
126 | 1.70k | BumpArena *ba = calloc(1, sizeof(BumpArena)); |
127 | 1.70k | ArenaPage *p = arena_page_new(ARENA_PAGE_SIZE); |
128 | 1.70k | ba->pages = p; |
129 | 1.70k | ba->first_page = p; |
130 | 1.70k | ba->total_bytes = 0; |
131 | 1.70k | return ba; |
132 | 1.70k | } |
133 | | |
134 | 1.70k | void bump_arena_free(BumpArena *ba) { |
135 | 1.70k | if (!ba) return; |
136 | 1.70k | arena_page_free_list(ba->first_page); |
137 | 1.70k | free(ba); |
138 | 1.70k | } |
139 | | |
140 | 6.47k | void bump_arena_reset(BumpArena *ba) { |
141 | 6.47k | if (!ba) return; |
142 | 12.9k | for (ArenaPage *p = ba->first_page; p; p = p->next) |
143 | 6.47k | p->used = 0; |
144 | 6.47k | ba->pages = ba->first_page; |
145 | 6.47k | ba->total_bytes = 0; |
146 | 6.47k | } |
147 | | |
148 | 629 | void *bump_alloc(BumpArena *ba, size_t size) { |
149 | 629 | size_t aligned = (size + 7) & ~(size_t)7; |
150 | 629 | ArenaPage *cur = ba->pages; |
151 | | |
152 | | /* Try current page */ |
153 | 629 | if (cur && cur->used + aligned <= cur->cap) { |
154 | 629 | void *ptr = cur->data + cur->used; |
155 | 629 | cur->used += aligned; |
156 | 629 | ba->total_bytes += aligned; |
157 | 629 | return ptr; |
158 | 629 | } |
159 | | |
160 | | /* Try next page in chain (from prior reset) */ |
161 | 0 | if (cur && cur->next && cur->next->used + aligned <= cur->next->cap) { |
162 | 0 | ba->pages = cur->next; |
163 | 0 | void *ptr = ba->pages->data + ba->pages->used; |
164 | 0 | ba->pages->used += aligned; |
165 | 0 | ba->total_bytes += aligned; |
166 | 0 | return ptr; |
167 | 0 | } |
168 | | |
169 | | /* Allocate a new page */ |
170 | 0 | size_t page_cap = aligned > ARENA_PAGE_SIZE ? aligned : ARENA_PAGE_SIZE; |
171 | 0 | ArenaPage *np = arena_page_new(page_cap); |
172 | 0 | if (cur) { |
173 | 0 | np->next = cur->next; |
174 | 0 | cur->next = np; |
175 | 0 | } else { |
176 | 0 | ba->first_page = np; |
177 | 0 | } |
178 | 0 | ba->pages = np; |
179 | 0 | void *ptr = np->data; |
180 | 0 | np->used = aligned; |
181 | 0 | ba->total_bytes += aligned; |
182 | 0 | return ptr; |
183 | 0 | } |
184 | | |
185 | 0 | char *bump_strdup(BumpArena *ba, const char *s) { |
186 | 0 | size_t len = strlen(s) + 1; |
187 | 0 | char *ptr = bump_alloc(ba, len); |
188 | 0 | memcpy(ptr, s, len); |
189 | 0 | return ptr; |
190 | 0 | } |
191 | | |
192 | | /* ── Crystal Region ── */ |
193 | | |
194 | 420 | static CrystalRegion *crystal_region_new(RegionId id, Epoch epoch) { |
195 | 420 | CrystalRegion *r = calloc(1, sizeof(CrystalRegion)); |
196 | 420 | r->id = id; |
197 | 420 | r->epoch = epoch; |
198 | 420 | r->pages = arena_page_new(ARENA_PAGE_SIZE); |
199 | 420 | r->total_bytes = 0; |
200 | 420 | return r; |
201 | 420 | } |
202 | | |
203 | 420 | static void crystal_region_free(CrystalRegion *r) { |
204 | 420 | if (!r) return; |
205 | 420 | arena_page_free_list(r->pages); |
206 | 420 | free(r); |
207 | 420 | } |
208 | | |
209 | | /* ── Arena allocation ── */ |
210 | | |
211 | 1.51k | void *arena_alloc(CrystalRegion *r, size_t size) { |
212 | | /* 8-byte alignment */ |
213 | 1.51k | size_t aligned = (size + 7) & ~(size_t)7; |
214 | | |
215 | | /* Try to fit in the current head page */ |
216 | 1.51k | ArenaPage *head = r->pages; |
217 | 1.51k | if (head && head->used + aligned <= head->cap) { |
218 | 1.49k | void *ptr = head->data + head->used; |
219 | 1.49k | head->used += aligned; |
220 | 1.49k | r->total_bytes += aligned; |
221 | 1.49k | return ptr; |
222 | 1.49k | } |
223 | | |
224 | | /* Need a new page — oversized allocs get a dedicated page */ |
225 | 24 | size_t page_cap = aligned > ARENA_PAGE_SIZE ? aligned : ARENA_PAGE_SIZE; |
226 | 24 | ArenaPage *np = arena_page_new(page_cap); |
227 | 24 | np->next = r->pages; |
228 | 24 | r->pages = np; |
229 | | |
230 | 24 | void *ptr = np->data; |
231 | 24 | np->used = aligned; |
232 | 24 | r->total_bytes += aligned; |
233 | 24 | return ptr; |
234 | 1.51k | } |
235 | | |
236 | 63 | void *arena_calloc(CrystalRegion *r, size_t count, size_t size) { |
237 | 63 | if (count > 0 && size > SIZE_MAX / count) return NULL; |
238 | 63 | size_t total = count * size; |
239 | 63 | void *ptr = arena_alloc(r, total); |
240 | 63 | memset(ptr, 0, total); |
241 | 63 | return ptr; |
242 | 63 | } |
243 | | |
244 | 639 | char *arena_strdup(CrystalRegion *r, const char *s) { |
245 | 639 | size_t len = strlen(s) + 1; |
246 | 639 | char *ptr = arena_alloc(r, len); |
247 | 639 | memcpy(ptr, s, len); |
248 | 639 | return ptr; |
249 | 639 | } |
250 | | |
251 | | /* ── Region Manager ── */ |
252 | | |
253 | 928 | RegionManager *region_manager_new(void) { |
254 | 928 | RegionManager *rm = calloc(1, sizeof(RegionManager)); |
255 | 928 | rm->cap = 8; |
256 | 928 | rm->regions = malloc(rm->cap * sizeof(CrystalRegion *)); |
257 | 928 | return rm; |
258 | 928 | } |
259 | | |
260 | 928 | void region_manager_free(RegionManager *rm) { |
261 | 928 | if (!rm) return; |
262 | 1.10k | for (size_t i = 0; i < rm->count; i++) { |
263 | 179 | crystal_region_free(rm->regions[i]); |
264 | 179 | } |
265 | 928 | free(rm->regions); |
266 | 928 | free(rm); |
267 | 928 | } |
268 | | |
269 | 7.40k | Epoch region_advance_epoch(RegionManager *rm) { |
270 | 7.40k | return ++rm->current_epoch; |
271 | 7.40k | } |
272 | | |
273 | 9 | Epoch region_current_epoch(const RegionManager *rm) { |
274 | 9 | return rm->current_epoch; |
275 | 9 | } |
276 | | |
277 | 420 | CrystalRegion *region_create(RegionManager *rm) { |
278 | 420 | if (rm->count >= rm->cap) { |
279 | 0 | rm->cap *= 2; |
280 | 0 | rm->regions = realloc(rm->regions, rm->cap * sizeof(CrystalRegion *)); |
281 | 0 | } |
282 | 420 | CrystalRegion *r = crystal_region_new(rm->next_id++, rm->current_epoch); |
283 | 420 | rm->regions[rm->count++] = r; |
284 | 420 | rm->total_allocs++; |
285 | 420 | if (rm->count > rm->peak_count) |
286 | 211 | rm->peak_count = rm->count; |
287 | 420 | return r; |
288 | 420 | } |
289 | | |
290 | 7.38k | size_t region_collect(RegionManager *rm, const RegionId *reachable, size_t reachable_count) { |
291 | 7.38k | size_t freed = 0; |
292 | 7.38k | size_t i = 0; |
293 | 21.0k | while (i < rm->count) { |
294 | 13.6k | bool is_reachable = false; |
295 | 20.2k | for (size_t j = 0; j < reachable_count; j++) { |
296 | 20.0k | if (rm->regions[i]->id == reachable[j]) { |
297 | 13.4k | is_reachable = true; |
298 | 13.4k | break; |
299 | 13.4k | } |
300 | 20.0k | } |
301 | 13.6k | if (!is_reachable) { |
302 | 241 | crystal_region_free(rm->regions[i]); |
303 | 241 | rm->regions[i] = rm->regions[rm->count - 1]; |
304 | 241 | rm->count--; |
305 | 241 | freed++; |
306 | 13.4k | } else { |
307 | 13.4k | i++; |
308 | 13.4k | } |
309 | 13.6k | } |
310 | 7.38k | return freed; |
311 | 7.38k | } |
312 | | |
313 | 45 | size_t region_count(const RegionManager *rm) { |
314 | 45 | return rm->count; |
315 | 45 | } |
316 | | |
317 | 15 | size_t region_total_allocs(const RegionManager *rm) { |
318 | 15 | return rm->total_allocs; |
319 | 15 | } |
320 | | |
321 | 24 | size_t region_live_data_bytes(const RegionManager *rm) { |
322 | 24 | size_t total = 0; |
323 | 54 | for (size_t i = 0; i < rm->count; i++) |
324 | 30 | total += rm->regions[i]->total_bytes; |
325 | 24 | return total; |
326 | 24 | } |
327 | | |
328 | | /* ── Dual Heap ── */ |
329 | | |
330 | 880 | DualHeap *dual_heap_new(void) { |
331 | 880 | DualHeap *dh = malloc(sizeof(DualHeap)); |
332 | 880 | dh->fluid = fluid_heap_new(); |
333 | 880 | dh->regions = region_manager_new(); |
334 | 880 | return dh; |
335 | 880 | } |
336 | | |
337 | 880 | void dual_heap_free(DualHeap *dh) { |
338 | 880 | if (!dh) return; |
339 | 880 | fluid_heap_free(dh->fluid); |
340 | 880 | region_manager_free(dh->regions); |
341 | 880 | free(dh); |
342 | 880 | } |