Graphviz 16.0.1~dev.20260815.2250
Loading...
Searching...
No Matches
list.c
Go to the documentation of this file.
1#ifndef NO_CONFIG // defined by test_list.c
2#include "config.h"
3#endif
4
5#include <assert.h>
6#include <errno.h>
7#include <stdbool.h>
8#include <stdint.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <util/alloc.h>
13#include <util/asan.h>
14#include <util/exit.h>
15#include <util/gv_math.h>
16#include <util/list-private.h>
17#include <util/prisize_t.h>
18#include <util/unused.h>
19
20static const void *slot_from_const_list(const list_t_ *list, size_t index,
21 size_t stride) {
22 assert(list != NULL);
23 assert(list->base != NULL || index == 0 || stride == 0);
24
25 const char *const base = list->base;
26 return base + index * stride;
27}
28
29static void *slot_from_list(list_t_ *list, size_t index, size_t stride) {
30 assert(list != NULL);
31 assert(list->base != NULL || index == 0 || stride == 0);
32
33 char *const base = list->base;
34 return base + index * stride;
35}
36
37static const void *slot_from_const_base(const void *base, size_t index,
38 size_t stride) {
39 assert(base != NULL || index == 0 || stride == 0);
40
41 const char *const b = base;
42 return b + index * stride;
43}
44
45static void *slot_from_base(void *base, size_t index, size_t stride) {
46 assert(base != NULL || index == 0 || stride == 0);
47
48 char *const b = base;
49 return b + index * stride;
50}
51
52#define INDEX_TO(origin, index, stride) \
53 (_Generic((origin), \
54 const list_t_ *: slot_from_const_list, \
55 list_t_ *: slot_from_list, \
56 const void *: slot_from_const_base, \
57 void *: slot_from_base)((origin), (index), (stride)))
58
59size_t gv_list_append_slot_(list_t_ *list, size_t item_size) {
60 assert(list != NULL);
61
62 // do we need to expand the backing storage?
63 if (list->size == list->capacity) {
64 const size_t c = list->capacity == 0 ? 1 : (list->capacity * 2);
65 gv_list_reserve_(list, c, item_size);
66 }
67
68 assert(list->capacity > 0);
69 assert(list->size < list->capacity);
70
71 // append the new slot
72 const size_t new_slot = (list->head + list->size) % list->capacity;
73 void *const slot = INDEX_TO(list, new_slot, item_size);
74 ASAN_UNPOISON(slot, item_size);
75 ++list->size;
76
77 return new_slot;
78}
79
80size_t gv_list_prepend_slot_(list_t_ *list, size_t item_size) {
81 assert(list != NULL);
82
83 // do we need to expand the backing storage?
84 if (list->size == list->capacity) {
85 const size_t c = list->capacity == 0 ? 1 : (list->capacity * 2);
86 gv_list_reserve_(list, c, item_size);
87 }
88
89 assert(list->capacity > 0);
90 assert(list->size < list->capacity);
91
92 // prepend the new slot
93 list->head = (list->head + (list->capacity - 1)) % list->capacity;
94 void *const slot = INDEX_TO(list, list->head, item_size);
95 ASAN_UNPOISON(slot, item_size);
96 ++list->size;
97
98 return list->head;
99}
100
101static int try_reserve(list_t_ *list, size_t capacity, size_t item_size) {
102 assert(list != NULL);
103
104 // if we can already fit enough items, nothing to do
105 if (list->capacity >= capacity) {
106 return 0;
107 }
108
109 // will the arithmetic below overflow?
110 assert(capacity > 0);
111 if (SIZE_MAX / capacity < item_size) {
112 return EOVERFLOW;
113 }
114
115 void *const base = realloc(list->base, capacity * item_size);
116 if (base == NULL && item_size > 0) {
117 return ENOMEM;
118 }
119
120 // zero the new memory
121 {
122 void *const new = INDEX_TO(base, list->capacity, item_size);
123 const size_t new_bytes = (capacity - list->capacity) * item_size;
124 if (new_bytes > 0) { // `new_bytes` can be 0 if `item_size == 0`
125 memset(new, 0, new_bytes);
126 }
127
128 // poison the new (conceptually unallocated) memory
129 ASAN_POISON(new, new_bytes);
130 }
131
132 // Do we need to shuffle the prefix upwards? E.g.
133 //
134 // ┌───┬───┬───┬───┐
135 // old: │ 3 │ 4 │ 1 │ 2 │
136 // └───┴───┴─┼─┴─┼─┘
137 // │ └───────────────┐
138 // └───────────────┐ │
139 // ▼ ▼
140 // ┌───┬───┬───┬───┬───┬───┬───┬───┐
141 // new: │ 3 │ 4 │ │ │ │ │ 1 │ 2 │
142 // └───┴───┴───┴───┴───┴───┴───┴───┘
143 // a b c d e f g h
144 if (list->head + list->size > list->capacity) {
145 const size_t prefix = list->capacity - list->head;
146 const size_t new_head = capacity - prefix;
147 // unpoison target range, slots [g, h] in example
148 void *const target = INDEX_TO(base, new_head, item_size);
149 ASAN_UNPOISON(target, prefix * item_size);
150 const void *const src = INDEX_TO(base, list->head, item_size);
151 if (prefix * item_size > 0) {
152 // `target` and `src` can be null when `item_size == 0`, and `memmove`
153 // would then be Undefined Behavior
154 memmove(target, src, prefix * item_size);
155 }
156 // (re-)poison new gap, slots [c, f] in example
157 void *const gap_begin = INDEX_TO(base, list->size - prefix, item_size);
158 ASAN_POISON(gap_begin, (list->capacity - list->size) * item_size);
159 list->head = new_head;
160 }
161
162 list->base = base;
163 list->capacity = capacity;
164 return 0;
165}
166
167bool gv_list_try_append_(list_t_ *list, const void *item, size_t item_size) {
168 assert(list != NULL);
169 assert(item != NULL);
170
171 // do we need to expand the backing storage?
172 if (list->size == list->capacity) {
173 do {
174 // can we attempt doubling without integer overflow?
175 if (SIZE_MAX / 2 >= list->capacity) {
176 const size_t c = list->capacity == 0 ? 1 : (list->capacity * 2);
177 if (try_reserve(list, c, item_size) == 0) {
178 // success
179 break;
180 }
181 }
182
183 // try a more conservative expansion
184 if (SIZE_MAX - 1 >= list->capacity) {
185 if (try_reserve(list, list->capacity + 1, item_size) == 0) {
186 // success
187 break;
188 }
189 }
190
191 // failed to expand the list
192 return false;
193 } while (0);
194 }
195
196 assert(list->size < list->capacity);
197
198 // we can now append, knowing it will not require backing storage expansion
199 const size_t new_slot = (list->head + list->size) % list->capacity;
200 void *const slot = INDEX_TO(list, new_slot, item_size);
201 ASAN_UNPOISON(slot, item_size);
202 if (item_size > 0) {
203 memcpy(slot, item, item_size);
204 }
205 ++list->size;
206
207 return true;
208}
209
210size_t gv_list_get_(const list_t_ list, size_t index) {
211 assert(index < list.size && "index out of bounds");
212 return (list.head + index) % list.capacity;
213}
214
215size_t gv_list_find_(const list_t_ list, const void *needle, size_t item_size) {
216
217 for (size_t i = 0; i < list.size; ++i) {
218 const size_t slot = gv_list_get_(list, i);
219 const void *candidate = INDEX_TO(&list, slot, item_size);
220 if (item_size == 0 || memcmp(needle, candidate, item_size) == 0) {
221 return i;
222 }
223 }
224
225 return SIZE_MAX;
226}
227
228void gv_list_remove_(list_t_ *list, size_t index, size_t item_size) {
229 assert(list != NULL);
230 assert(index < list->size);
231
232 // shrink the list
233 for (size_t i = index + 1; i < list->size; ++i) {
234 const size_t dst_slot = gv_list_get_(*list, i - 1);
235 void *const dst = INDEX_TO(list, dst_slot, item_size);
236 const size_t src_slot = gv_list_get_(*list, i);
237 const void *const src = INDEX_TO(list, src_slot, item_size);
238 if (item_size > 0) {
239 memcpy(dst, src, item_size);
240 }
241 }
242 const size_t truncated_slot = gv_list_get_(*list, list->size - 1);
243 void *truncated = INDEX_TO(list, truncated_slot, item_size);
244 ASAN_POISON(truncated, item_size);
245 --list->size;
246}
247
248void gv_list_clear_(list_t_ *list, size_t item_size) {
249 assert(list != NULL);
250
251 for (size_t i = 0; i < list->size; ++i) {
252 const size_t slot = gv_list_get_(*list, i);
253 void *const to_poison = INDEX_TO(list, slot, item_size);
254 ASAN_POISON(to_poison, item_size);
255 }
256
257 list->size = 0;
258
259 // opportunistically re-sync the list
260 list->head = 0;
261}
262
263void gv_list_reserve_(list_t_ *list, size_t capacity, size_t item_size) {
264 const int err = try_reserve(list, capacity, item_size);
265 if (err != 0) {
266 fprintf(stderr,
267 "failed to reserve %" PRISIZE_T " elements of size %" PRISIZE_T
268 " bytes: %s\n",
269 capacity, item_size, strerror(err));
270 graphviz_exit(EXIT_FAILURE);
271 }
272}
273
274bool gv_list_contains_(const list_t_ list, const void *needle,
275 size_t item_size) {
276 return gv_list_find_(list, needle, item_size) != SIZE_MAX;
277}
278
279list_t_ gv_list_copy_(const list_t_ list, size_t item_size) {
280 list_t_ ret = {.base = gv_calloc(list.capacity, item_size),
281 .capacity = list.capacity};
282
283 // opportunistically create the new list synced
284 for (size_t i = 0; i < list.size; ++i) {
285 const size_t slot = gv_list_get_(list, i);
286 const void *const src = INDEX_TO(&list, slot, item_size);
287 void *const dst = INDEX_TO(&ret, ret.size, item_size);
288 assert(ret.size < ret.capacity);
289 if (item_size > 0) {
290 memcpy(dst, src, item_size);
291 }
292 ++ret.size;
293 }
294
295 // mark the remainder of the allocated space as inaccessible
296 void *const to_poison = INDEX_TO(&ret, ret.size, item_size);
297 const size_t to_poison_len = (ret.capacity - ret.size) * item_size;
298 ASAN_POISON(to_poison, to_poison_len);
299
300 return ret;
301}
302
320static UNUSED bool is_contiguous(const list_t_ list) {
321 return list.head + list.size <= list.capacity;
322}
323
324void gv_list_sync_(list_t_ *list, size_t item_size) {
325 assert(list != NULL);
326
327 // Allow unrestricted access. The shuffle below accesses both allocated
328 // and unallocated elements, so just let it read and write everything.
329 ASAN_UNPOISON(list->base, list->capacity * item_size);
330
331 // Shuffle the list 1-1 until it is aligned. This is not efficient, but
332 // we assume this is a relatively rare operation.
333 while (list->head != 0) {
334 // rotate the list leftwards by 1
335 assert(list->capacity > 0);
336 // shuffle byte-by-byte to avoid dynamic allocation
337 for (size_t i = 0; i < item_size; ++i) {
338 uint8_t lowest;
339 memcpy(&lowest, list->base, sizeof(lowest));
340 const size_t remainder = list->capacity * item_size - sizeof(lowest);
341 memmove(list->base, (char *)list->base + sizeof(lowest), remainder);
342 memcpy((char *)list->base + remainder, &lowest, sizeof(lowest));
343 }
344 --list->head;
345 }
346
347 /* synchronization should have ensured the list no longer wraps */
348 assert(is_contiguous(*list));
349
350 /* re-establish access restrictions */
351 void *end = INDEX_TO(list, list->size, item_size);
352 ASAN_POISON(end, (list->capacity - list->size) * item_size);
353}
354
355void gv_list_sort_(list_t_ *list, int (*cmp)(const void *, const void *),
356 size_t item_size) {
357 assert(list != NULL);
358 assert(cmp != NULL);
359
360 gv_list_sync_(list, item_size);
361
362 if (list->size > 0 && item_size > 0) {
363 qsort(list->base, list->size, item_size, cmp);
364 }
365}
366
367static void exchange(void *a, void *b, size_t size) {
368 assert(a != NULL);
369 assert(b != NULL);
370
371 // do a byte-by-byte swap of the two objects
372 char *x = a;
373 char *y = b;
374 for (size_t i = 0; i < size; ++i) {
375 SWAP(&x[i], &y[i]);
376 }
377}
378
379void gv_list_reverse_(list_t_ *list, size_t item_size) {
380 assert(list != NULL);
381
382 // move from the outside inwards, swapping elements
383 for (size_t i = 0; i < list->size / 2; ++i) {
384 const size_t a = gv_list_get_(*list, i);
385 const size_t b = gv_list_get_(*list, list->size - i - 1);
386 void *const x = INDEX_TO(list, a, item_size);
387 void *const y = INDEX_TO(list, b, item_size);
388 exchange(x, y, item_size);
389 }
390}
391
392void gv_list_shrink_to_fit_(list_t_ *list, size_t item_size) {
393 assert(list != NULL);
394
395 gv_list_sync_(list, item_size);
396
397 if (list->capacity > list->size) {
398 list->base = gv_recalloc(list->base, list->capacity, list->size, item_size);
399 list->capacity = list->size;
400 }
401}
402
404 assert(list != NULL);
405 free(list->base);
406 *list = (list_t_){0};
407}
408
409void gv_list_pop_front_(list_t_ *list, void *into, size_t item_size) {
410 assert(list != NULL);
411 assert(list->size > 0);
412 assert(into != NULL);
413
414 // find and pop the first slot
415 const size_t slot = gv_list_get_(*list, 0);
416 void *const to_pop = INDEX_TO(list, slot, item_size);
417 if (item_size > 0) {
418 memcpy(into, to_pop, item_size);
419 }
420 ASAN_POISON(to_pop, item_size);
421 list->head = (list->head + 1) % list->capacity;
422 --list->size;
423}
424
425void gv_list_pop_back_(list_t_ *list, void *into, size_t item_size) {
426 assert(list != NULL);
427 assert(list->size > 0);
428 assert(into != NULL);
429
430 // find and pop last slot
431 const size_t slot = gv_list_get_(*list, list->size - 1);
432 void *const to_pop = INDEX_TO(list, slot, item_size);
433 if (item_size > 0) {
434 memcpy(into, to_pop, item_size);
435 }
436 ASAN_POISON(to_pop, item_size);
437 --list->size;
438}
439
440void gv_list_detach_(list_t_ *list, void *datap, size_t *sizep,
441 size_t item_size) {
442 assert(list != NULL);
443 assert(datap != NULL);
444
445 gv_list_sync_(list, item_size);
446 memcpy(datap, &list->base, sizeof(void *));
447 if (sizep != NULL) {
448 *sizep = list->size;
449 }
450
451 *list = (list_t_){0};
452}
Memory allocation wrappers that exit on failure.
static void * gv_recalloc(void *ptr, size_t old_nmemb, size_t new_nmemb, size_t size)
Definition alloc.h:73
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
macros for interacting with Address Sanitizer
#define ASAN_POISON(addr, size)
Definition asan.h:15
#define ASAN_UNPOISON(addr, size)
Definition asan.h:22
static char * err
Definition delaunay.c:518
static NORETURN void graphviz_exit(int status)
Definition exit.h:23
static int cmp(const void *key, const void *candidate)
void free(void *)
require define api prefix
Definition gmlparse.y:17
#define SIZE_MAX
Definition gmlscan.c:347
node NULL
Definition grammar.y:181
Arithmetic helper functions.
#define SWAP(a, b)
Definition gv_math.h:137
internal implementation details of list.h
static void exchange(void *a, void *b, size_t size)
Definition list.c:367
list_t_ gv_list_copy_(const list_t_ list, size_t item_size)
Definition list.c:279
bool gv_list_try_append_(list_t_ *list, const void *item, size_t item_size)
Definition list.c:167
static const void * slot_from_const_list(const list_t_ *list, size_t index, size_t stride)
Definition list.c:20
size_t gv_list_prepend_slot_(list_t_ *list, size_t item_size)
Definition list.c:80
void gv_list_pop_front_(list_t_ *list, void *into, size_t item_size)
Definition list.c:409
static const void * slot_from_const_base(const void *base, size_t index, size_t stride)
Definition list.c:37
#define INDEX_TO(origin, index, stride)
Definition list.c:52
bool gv_list_contains_(const list_t_ list, const void *needle, size_t item_size)
Definition list.c:274
size_t gv_list_append_slot_(list_t_ *list, size_t item_size)
Definition list.c:59
void gv_list_reserve_(list_t_ *list, size_t capacity, size_t item_size)
Definition list.c:263
void gv_list_free_(list_t_ *list)
Definition list.c:403
static void * slot_from_base(void *base, size_t index, size_t stride)
Definition list.c:45
void gv_list_clear_(list_t_ *list, size_t item_size)
Definition list.c:248
void gv_list_detach_(list_t_ *list, void *datap, size_t *sizep, size_t item_size)
Definition list.c:440
void gv_list_shrink_to_fit_(list_t_ *list, size_t item_size)
Definition list.c:392
void gv_list_sort_(list_t_ *list, int(*cmp)(const void *, const void *), size_t item_size)
Definition list.c:355
void gv_list_reverse_(list_t_ *list, size_t item_size)
Definition list.c:379
static UNUSED bool is_contiguous(const list_t_ list)
Definition list.c:320
void gv_list_pop_back_(list_t_ *list, void *into, size_t item_size)
Definition list.c:425
size_t gv_list_find_(const list_t_ list, const void *needle, size_t item_size)
Definition list.c:215
void gv_list_sync_(list_t_ *list, size_t item_size)
Definition list.c:324
static int try_reserve(list_t_ *list, size_t capacity, size_t item_size)
Definition list.c:101
static void * slot_from_list(list_t_ *list, size_t index, size_t stride)
Definition list.c:29
void gv_list_remove_(list_t_ *list, size_t index, size_t item_size)
Definition list.c:228
size_t gv_list_get_(const list_t_ list, size_t index)
Definition list.c:210
#define PRISIZE_T
Definition prisize_t.h:25
Definition utils.c:752
size_t size
size <= capacity
size_t capacity
available storage slots
void * base
(base == NULL && capacity == 0) || (base != NULL && capacity > 0)
size_t head
(capacity == 0 && head == 0) || (capacity > 0 && head < capacity)
abstraction for squashing compiler warnings for unused symbols
#define UNUSED
Definition unused.h:25