Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
memory_arena.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel
3 Copyright (C) 2026 - Aurélien PIERRE
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#define _GNU_SOURCE
20
21#include "common/darktable.h"
22#include "common/memory_arena.h"
23
24#include <errno.h>
25#include <stdio.h>
26#include <string.h>
27
28#ifdef _WIN32
29#include <windows.h>
30#else
31#include <sys/mman.h>
32#endif
33
34typedef struct dt_free_run_t
35{
36 uint32_t start;
37 uint32_t length;
39
41 size_t size,
42 uint32_t *out_pages,
43 size_t *out_size)
44{
45 if(IS_NULL_PTR(a) || IS_NULL_PTR(a->base) || IS_NULL_PTR(out_pages) || a->page_size == 0 || a->num_pages == 0) return FALSE;
46 if(size == 0) return FALSE;
47 if(size > SIZE_MAX - (a->page_size - 1)) return FALSE;
48
49 const size_t pages = (size + a->page_size - 1) / a->page_size;
50 if(pages > a->num_pages || pages > UINT32_MAX) return FALSE;
51 if(out_size)
52 {
53 if(pages > SIZE_MAX / a->page_size) return FALSE;
54 *out_size = pages * a->page_size;
55 }
56
57 *out_pages = (uint32_t)pages;
58 return TRUE;
59}
60
61/*
62 * Allocate from the arena in page-sized chunks.
63 * Uses a best-fit scan over the sorted free-run list, then consumes from
64 * the beginning of the selected run. On success, returns a pointer into the
65 * arena and writes the page-rounded allocation size to out_size.
66 */
68 size_t size,
69 size_t *out_size)
70{
71 if(IS_NULL_PTR(a) || IS_NULL_PTR(a->base) || !out_size) return NULL;
72
73 uint32_t pages_needed = 0;
74 size_t rounded_size = 0;
75 if(!dt_cache_arena_calc(a, size, &pages_needed, &rounded_size)) return NULL;
76
78
79 guint best_index = G_MAXUINT;
80 uint32_t best_length = UINT32_MAX;
81
82 for(guint i = 0; i < a->free_runs->len; i++)
83 {
84 dt_free_run_t *r = &g_array_index(a->free_runs, dt_free_run_t, i);
85 if(r->length >= pages_needed && r->length < best_length)
86 {
87 best_index = i;
88 best_length = r->length;
89 if(best_length == pages_needed) break; // exact fit
90 }
91 }
92
93 if(best_index == G_MAXUINT)
94 {
96 return NULL;
97 }
98
99 dt_free_run_t *r = &g_array_index(a->free_runs, dt_free_run_t, best_index);
100 const uint32_t first = r->start;
101
102 /* consume from the front of the run so the list stays sorted */
103 r->start += pages_needed;
104 r->length -= pages_needed;
105
106 /* remove empty run after consumption */
107 if(r->length == 0)
108 g_array_remove_index(a->free_runs, best_index);
109
111
112 *out_size = rounded_size;
113 return a->base + (size_t)first * a->page_size;
114}
115
116
117/*
118 * Return a previously allocated region to the arena.
119 * The pointer must refer to the arena base, and size is rounded up to pages.
120 * The freed run is inserted in order and coalesced with adjacent runs.
121 */
123 void *ptr,
124 size_t size)
125{
126 if(IS_NULL_PTR(a) || IS_NULL_PTR(a->base) || !a->free_runs || a->page_size == 0 || a->num_pages == 0)
127 return;
128 if(IS_NULL_PTR(ptr) || size == 0) return;
129
130 const uintptr_t base = (uintptr_t)a->base;
131 const uintptr_t addr = (uintptr_t)ptr;
132 if(addr < base || addr >= base + a->size)
133 {
134 fprintf(stderr, "[pixelpipe] arena free: pointer out of range\n");
135 return;
136 }
137 if(((addr - base) % a->page_size) != 0)
138 {
139 fprintf(stderr, "[pixelpipe] arena free: pointer not page-aligned\n");
140 return;
141 }
142
143 uint32_t pages = 0;
144 if(!dt_cache_arena_calc(a, size, &pages, NULL))
145 {
146 fprintf(stderr, "[pixelpipe] arena free: invalid size\n");
147 return;
148 }
149
150 const size_t first_sz = (addr - base) / a->page_size;
151 if(first_sz >= a->num_pages || pages > a->num_pages - first_sz)
152 {
153 fprintf(stderr, "[pixelpipe] arena free: range out of bounds\n");
154 return;
155 }
156
157 const uint32_t first = (uint32_t)first_sz;
158
160
161 /* insert a new free run, keeping free_runs sorted by start page */
162 guint i = 0;
163 while(i < a->free_runs->len &&
164 g_array_index(a->free_runs, dt_free_run_t, i).start < first)
165 i++;
166
167 if(i > 0)
168 {
169 dt_free_run_t *prev = &g_array_index(a->free_runs, dt_free_run_t, i - 1);
170 if(prev->start + prev->length > first)
171 {
173 fprintf(stderr, "[pixelpipe] arena free: overlap with previous run\n");
174 return;
175 }
176 }
177 if(i < a->free_runs->len)
178 {
179 dt_free_run_t *next = &g_array_index(a->free_runs, dt_free_run_t, i);
180 if(first + pages > next->start)
181 {
183 fprintf(stderr, "[pixelpipe] arena free: overlap with next run\n");
184 return;
185 }
186 }
187
188 dt_free_run_t new = { first, pages };
189 g_array_insert_val(a->free_runs, i, new);
190
191 /* coalesce with next run if adjacent */
192 if(i + 1 < a->free_runs->len)
193 {
194 dt_free_run_t *cur = &g_array_index(a->free_runs, dt_free_run_t, i);
195 dt_free_run_t *next = &g_array_index(a->free_runs, dt_free_run_t, i + 1);
196 if(cur->start + cur->length == next->start)
197 {
198 cur->length += next->length;
199 g_array_remove_index(a->free_runs, i + 1);
200 }
201 }
202
203 /* coalesce with previous run if adjacent */
204 if(i > 0)
205 {
206 dt_free_run_t *prev = &g_array_index(a->free_runs, dt_free_run_t, i - 1);
207 dt_free_run_t *cur = &g_array_index(a->free_runs, dt_free_run_t, i);
208 if(prev->start + prev->length == cur->start)
209 {
210 prev->length += cur->length;
211 g_array_remove_index(a->free_runs, i);
212 }
213 }
214
216}
217
219 uint32_t *out_total_free_pages,
220 uint32_t *out_largest_free_run_pages)
221{
222 if(out_total_free_pages) *out_total_free_pages = 0;
223 if(out_largest_free_run_pages) *out_largest_free_run_pages = 0;
224 if(IS_NULL_PTR(a) || !a->free_runs) return;
225
227 uint32_t total = 0;
228 uint32_t largest = 0;
229 for(guint i = 0; i < a->free_runs->len; i++)
230 {
231 dt_free_run_t *r = &g_array_index(a->free_runs, dt_free_run_t, i);
232 total += r->length;
233 if(r->length > largest) largest = r->length;
234 }
236
237 if(out_total_free_pages) *out_total_free_pages = total;
238 if(out_largest_free_run_pages) *out_largest_free_run_pages = largest;
239}
240
242{
243 if(IS_NULL_PTR(a)) return;
244
246 g_array_free(a->free_runs, TRUE);
248
250
251 /* 5. Release the virtual memory block */
252 if(a->base && a->size)
253 {
254#ifdef _WIN32
255 VirtualFree(a->base, 0, MEM_RELEASE);
256#else
257 munmap(a->base, a->size);
258#endif
259 }
260
261 /* 6. Poison the struct (defensive) */
262 a->base = NULL;
263 a->size = 0;
264 a->num_pages = 0;
265 a->page_size = 0;
266}
267
268// return 0 on success 1 on error
269int dt_cache_arena_init(dt_cache_arena_t *a, size_t total_size)
270{
271 const size_t page_size = 64 * 1024; // 64 KiB cache pages
272 const size_t pages = total_size / page_size;
273
274#ifdef _WIN32
275 a->base = (uint8_t *)VirtualAlloc(NULL, total_size,
276 MEM_RESERVE | MEM_COMMIT,
277 PAGE_READWRITE);
278 if(IS_NULL_PTR(a->base))
279 {
280 const DWORD err = GetLastError();
281 fprintf(stderr, "couldn't alloc map (VirtualAlloc error %lu)\n", (unsigned long)err);
282 return 1;
283 }
284#else
285 a->base = mmap(NULL, total_size,
286 PROT_READ | PROT_WRITE,
287 MAP_PRIVATE | MAP_ANONYMOUS,
288 -1, 0);
289
290 if(a->base == MAP_FAILED)
291 {
292 a->base = NULL;
293 fprintf(stderr, "couldn't alloc map (mmap error %d: %s)\n", errno, strerror(errno));
294 return 1;
295 }
296#endif
297
298 a->size = total_size;
299 a->page_size = page_size;
300 a->num_pages = pages;
301
302 a->free_runs = g_array_new(FALSE, FALSE, sizeof(dt_free_run_t));
303 if(!a->free_runs)
304 {
305#ifdef _WIN32
306 VirtualFree(a->base, 0, MEM_RELEASE);
307#else
308 munmap(a->base, a->size);
309#endif
310 a->base = NULL;
311 a->size = 0;
312 a->page_size = 0;
313 a->num_pages = 0;
314 fprintf(stderr, "couldn't alloc free run list\n");
315 return 1;
316 }
317
318 /* start with one free run covering the whole arena */
319 dt_free_run_t full = {
320 .start = 0,
321 .length = a->num_pages
322 };
323
324 g_array_append_val(a->free_runs, full);
325
326 dt_pthread_mutex_init(&a->lock, NULL);
327 return 0;
328}
329
330gboolean dt_cache_arena_ptr_in(const dt_cache_arena_t *a, const void *ptr)
331{
332 if(IS_NULL_PTR(a) || IS_NULL_PTR(a->base) || IS_NULL_PTR(ptr)) return FALSE;
333 const uintptr_t base = (uintptr_t)a->base;
334 const uintptr_t addr = (uintptr_t)ptr;
335 return addr >= base && addr < base + a->size;
336}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Definition dtpthread.h:359
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:379
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:364
void dt_cache_arena_stats(dt_cache_arena_t *a, uint32_t *out_total_free_pages, uint32_t *out_largest_free_run_pages)
void dt_cache_arena_cleanup(dt_cache_arena_t *a)
gboolean dt_cache_arena_ptr_in(const dt_cache_arena_t *a, const void *ptr)
gboolean dt_cache_arena_calc(const dt_cache_arena_t *a, size_t size, uint32_t *out_pages, size_t *out_size)
int dt_cache_arena_init(dt_cache_arena_t *a, size_t total_size)
void dt_cache_arena_free(dt_cache_arena_t *a, void *ptr, size_t size)
void * dt_cache_arena_alloc(dt_cache_arena_t *a, size_t size, size_t *out_size)
size_t size
Definition mipmap_cache.c:3
const float r
dt_pthread_mutex_t lock
uint32_t start
uint32_t length