Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}