File Coverage

graph.h
Criterion Covered Total %
statement 161 183 87.9
branch 54 128 42.1
condition n/a
subroutine n/a
pod n/a
total 215 311 69.1


line stmt bran cond sub pod time code
1             /*
2             * graph.h -- Shared-memory directed weighted graph for Linux
3             *
4             * Nodes allocated from a bitmap pool. Edges stored as adjacency lists
5             * in a separate edge pool. Mutex-protected mutations.
6             *
7             * Node: int64_t data
8             * Edge: uint32_t dst, int64_t weight, uint32_t next (linked list)
9             */
10              
11             #ifndef GRAPH_H
12             #define GRAPH_H
13              
14             #include
15             #include
16             #include
17             #include
18             #include
19             #include
20             #include
21             #include
22             #include
23             #include
24             #include
25             #include
26             #include
27             #include
28             #include
29             #include
30              
31             #define GRAPH_MAGIC 0x47525031U /* "GRP1" */
32             #define GRAPH_VERSION 1
33             #define GRAPH_ERR_BUFLEN 256
34             #define GRAPH_NONE UINT32_MAX
35              
36             #define GRAPH_MUTEX_BIT 0x80000000U
37             #define GRAPH_MUTEX_PID 0x7FFFFFFFU
38              
39             /* ================================================================
40             * Layout
41             *
42             * Header (128 bytes)
43             * Node data: max_nodes * sizeof(int64_t) — node values
44             * Node heads: max_nodes * sizeof(uint32_t) — first-edge index per node
45             * Node bitmap: ceil(max_nodes/64) * 8 — allocation bitmap
46             * Edges: max_edges * sizeof(GraphEdge) — edge pool
47             * Edge bitmap: ceil(max_edges/64) * 8 — edge allocation bitmap
48             * ================================================================ */
49              
50             typedef struct {
51             uint32_t dst;
52             uint32_t next; /* index of next edge in adjacency list, GRAPH_NONE = end */
53             int64_t weight;
54             } GraphEdge;
55              
56             typedef struct {
57             uint32_t magic;
58             uint32_t version;
59             uint32_t max_nodes;
60             uint32_t max_edges;
61             uint64_t total_size;
62             uint64_t node_data_off;
63             uint64_t node_heads_off;
64             uint64_t node_bitmap_off;
65             uint64_t edge_data_off;
66             uint64_t edge_bitmap_off;
67              
68             uint32_t node_count; /* 64 */
69             uint32_t edge_count; /* 68 */
70             uint32_t mutex; /* 72 */
71             uint32_t mutex_waiters; /* 76 */
72             uint64_t stat_ops; /* 80 */
73             uint8_t _pad1[40]; /* 88-127 */
74             } GraphHeader;
75              
76             #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
77             _Static_assert(sizeof(GraphHeader) == 128, "GraphHeader must be 128 bytes");
78             #endif
79              
80             typedef struct {
81             GraphHeader *hdr;
82             int64_t *node_data;
83             uint32_t *node_heads;
84             uint64_t *node_bitmap;
85             GraphEdge *edges;
86             uint64_t *edge_bitmap;
87             uint32_t node_bwords;
88             uint32_t edge_bwords;
89             size_t mmap_size;
90             char *path;
91             int notify_fd;
92             int backing_fd;
93             } GraphHandle;
94              
95             /* ================================================================
96             * Mutex (same pattern as Heap)
97             * ================================================================ */
98              
99             static const struct timespec graph_lock_timeout = { 2, 0 };
100              
101 0           static inline int graph_pid_alive(uint32_t pid) {
102 0 0         if (pid == 0) return 1;
103 0 0         return !(kill((pid_t)pid, 0) == -1 && errno == ESRCH);
    0          
104             }
105              
106 22           static inline void graph_mutex_lock(GraphHeader *hdr) {
107 22           uint32_t mypid = GRAPH_MUTEX_BIT | ((uint32_t)getpid() & GRAPH_MUTEX_PID);
108 22           for (int spin = 0; ; spin++) {
109 22           uint32_t expected = 0;
110 22 50         if (__atomic_compare_exchange_n(&hdr->mutex, &expected, mypid,
111             1, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
112 22           return;
113 0 0         if (spin < 32) {
114             #if defined(__x86_64__) || defined(__i386__)
115 0           __asm__ volatile("pause" ::: "memory");
116             #elif defined(__aarch64__)
117             __asm__ volatile("yield" ::: "memory");
118             #endif
119 0           continue;
120             }
121 0           __atomic_add_fetch(&hdr->mutex_waiters, 1, __ATOMIC_RELAXED);
122 0           uint32_t cur = __atomic_load_n(&hdr->mutex, __ATOMIC_RELAXED);
123 0 0         if (cur != 0) {
124 0           long rc = syscall(SYS_futex, &hdr->mutex, FUTEX_WAIT, cur,
125             &graph_lock_timeout, NULL, 0);
126 0 0         if (rc == -1 && errno == ETIMEDOUT && cur >= GRAPH_MUTEX_BIT) {
    0          
    0          
127 0           uint32_t pid = cur & GRAPH_MUTEX_PID;
128 0 0         if (!graph_pid_alive(pid))
129 0           __atomic_compare_exchange_n(&hdr->mutex, &cur, 0,
130             0, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED);
131             }
132             }
133 0           __atomic_sub_fetch(&hdr->mutex_waiters, 1, __ATOMIC_RELAXED);
134 0           spin = 0;
135             }
136             }
137              
138 22           static inline void graph_mutex_unlock(GraphHeader *hdr) {
139 22           __atomic_store_n(&hdr->mutex, 0, __ATOMIC_RELEASE);
140 22 50         if (__atomic_load_n(&hdr->mutex_waiters, __ATOMIC_RELAXED) > 0)
141 0           syscall(SYS_futex, &hdr->mutex, FUTEX_WAKE, 1, NULL, NULL, 0);
142 22           }
143              
144             /* ================================================================
145             * Bitmap helpers
146             * ================================================================ */
147              
148 34           static inline int graph_bit_set(uint64_t *bm, uint32_t idx) {
149 34           uint64_t word = __atomic_load_n(&bm[idx / 64], __ATOMIC_ACQUIRE);
150 34           return (word >> (idx % 64)) & 1;
151             }
152              
153 10           static inline int32_t graph_bit_alloc(uint64_t *bm, uint32_t bwords, uint32_t max) {
154 10 50         for (uint32_t w = 0; w < bwords; w++) {
155 10           uint64_t word = bm[w];
156 10 50         if (word == ~(uint64_t)0) continue;
157 10           int bit = __builtin_ctzll(~word);
158 10           uint32_t idx = w * 64 + bit;
159 10 50         if (idx >= max) return -1;
160 10           bm[w] |= (uint64_t)1 << bit;
161 10           return (int32_t)idx;
162             }
163 0           return -1;
164             }
165              
166 2           static inline void graph_bit_free(uint64_t *bm, uint32_t idx) {
167 2           bm[idx / 64] &= ~((uint64_t)1 << (idx % 64));
168 2           }
169              
170             /* ================================================================
171             * Graph operations (must hold mutex)
172             * ================================================================ */
173              
174 5           static inline int32_t graph_add_node_locked(GraphHandle *h, int64_t data) {
175 5           int32_t idx = graph_bit_alloc(h->node_bitmap, h->node_bwords, h->hdr->max_nodes);
176 5 50         if (idx < 0) return -1;
177 5           h->node_data[idx] = data;
178 5           h->node_heads[idx] = GRAPH_NONE;
179 5           h->hdr->node_count++;
180 5           return idx;
181             }
182              
183 6           static inline int graph_add_edge_locked(GraphHandle *h, uint32_t src, uint32_t dst, int64_t weight) {
184 6 50         if (src >= h->hdr->max_nodes || dst >= h->hdr->max_nodes) return 0;
    50          
185 6 100         if (!graph_bit_set(h->node_bitmap, src) || !graph_bit_set(h->node_bitmap, dst))
    50          
186 1           return 0;
187 5           int32_t eidx = graph_bit_alloc(h->edge_bitmap, h->edge_bwords, h->hdr->max_edges);
188 5 50         if (eidx < 0) return 0;
189 5           h->edges[eidx].dst = dst;
190 5           h->edges[eidx].weight = weight;
191 5           h->edges[eidx].next = h->node_heads[src];
192 5           h->node_heads[src] = (uint32_t)eidx;
193 5           h->hdr->edge_count++;
194 5           return 1;
195             }
196              
197 1           static inline int graph_remove_node_locked(GraphHandle *h, uint32_t node) {
198 1 50         if (node >= h->hdr->max_nodes) return 0;
199 1 50         if (!graph_bit_set(h->node_bitmap, node)) return 0;
200             /* free all outgoing edges */
201 1           uint32_t eidx = h->node_heads[node];
202 2 100         while (eidx != GRAPH_NONE) {
203 1           uint32_t next = h->edges[eidx].next;
204 1           graph_bit_free(h->edge_bitmap, eidx);
205 1           h->hdr->edge_count--;
206 1           eidx = next;
207             }
208 1           h->node_heads[node] = GRAPH_NONE;
209 1           graph_bit_free(h->node_bitmap, node);
210 1           h->hdr->node_count--;
211 1           return 1;
212             }
213              
214             /* ================================================================
215             * Public API (lock + operation + unlock)
216             * ================================================================ */
217              
218 5           static inline int32_t graph_add_node(GraphHandle *h, int64_t data) {
219 5           graph_mutex_lock(h->hdr);
220 5           int32_t r = graph_add_node_locked(h, data);
221 5           h->hdr->stat_ops++;
222 5           graph_mutex_unlock(h->hdr);
223 5           return r;
224             }
225              
226 6           static inline int graph_add_edge(GraphHandle *h, uint32_t src, uint32_t dst, int64_t weight) {
227 6           graph_mutex_lock(h->hdr);
228 6           int r = graph_add_edge_locked(h, src, dst, weight);
229 6           h->hdr->stat_ops++;
230 6           graph_mutex_unlock(h->hdr);
231 6           return r;
232             }
233              
234 1           static inline int graph_remove_node(GraphHandle *h, uint32_t node) {
235 1           graph_mutex_lock(h->hdr);
236 1           int r = graph_remove_node_locked(h, node);
237 1           h->hdr->stat_ops++;
238 1           graph_mutex_unlock(h->hdr);
239 1           return r;
240             }
241              
242 13           static inline int graph_has_node(GraphHandle *h, uint32_t node) {
243 13 100         if (node >= h->hdr->max_nodes) return 0;
244 12           return graph_bit_set(h->node_bitmap, node);
245             }
246              
247             static inline int64_t graph_node_data(GraphHandle *h, uint32_t node) {
248             return h->node_data[node];
249             }
250              
251             static inline void graph_set_node_data(GraphHandle *h, uint32_t node, int64_t data) {
252             graph_mutex_lock(h->hdr);
253             h->node_data[node] = data;
254             graph_mutex_unlock(h->hdr);
255             }
256              
257             /* Returns count of neighbors written to out_dst/out_weight (up to max_out).
258             * Call with out_dst=NULL to just count. */
259 3           static inline uint32_t graph_neighbors(GraphHandle *h, uint32_t node,
260             uint32_t *out_dst, int64_t *out_weight,
261             uint32_t max_out) {
262 3           uint32_t count = 0;
263 3           uint32_t eidx = h->node_heads[node];
264 6 100         while (eidx != GRAPH_NONE) {
265 3 50         if (out_dst && count < max_out) {
    0          
266 0           out_dst[count] = h->edges[eidx].dst;
267 0           out_weight[count] = h->edges[eidx].weight;
268             }
269 3           count++;
270 3           eidx = h->edges[eidx].next;
271             }
272 3           return count;
273             }
274              
275             /* ================================================================
276             * Create / Open / Close
277             * ================================================================ */
278              
279             #define GRAPH_ERR(fmt, ...) do { if (errbuf) snprintf(errbuf, GRAPH_ERR_BUFLEN, fmt, ##__VA_ARGS__); } while(0)
280              
281 3           static GraphHandle *graph_create(const char *path, uint32_t max_nodes, uint32_t max_edges,
282             char *errbuf) {
283 3 50         if (errbuf) errbuf[0] = '\0';
284 3 50         if (max_nodes == 0) { GRAPH_ERR("max_nodes must be > 0"); return NULL; }
    0          
285 3 50         if (max_edges == 0) { GRAPH_ERR("max_edges must be > 0"); return NULL; }
    0          
286 3 50         if (max_nodes > UINT32_MAX - 63) { GRAPH_ERR("max_nodes too large"); return NULL; }
    0          
287 3 50         if (max_edges > UINT32_MAX - 63) { GRAPH_ERR("max_edges too large"); return NULL; }
    0          
288              
289 3           uint32_t nb = (max_nodes + 63) / 64;
290 3           uint32_t eb = (max_edges + 63) / 64;
291              
292 3           uint64_t node_data_off = sizeof(GraphHeader);
293 3           uint64_t node_heads_off = node_data_off + (uint64_t)max_nodes * sizeof(int64_t);
294 3           uint64_t node_bitmap_off = node_heads_off + (uint64_t)max_nodes * sizeof(uint32_t);
295 3           uint64_t edge_data_off = node_bitmap_off + (uint64_t)nb * 8;
296 3           uint64_t edge_bitmap_off = edge_data_off + (uint64_t)max_edges * sizeof(GraphEdge);
297 3           uint64_t total = edge_bitmap_off + (uint64_t)eb * 8;
298              
299 3           int anonymous = (path == NULL);
300 3           int fd = -1;
301             size_t map_size;
302             void *base;
303              
304 3 100         if (anonymous) {
305 1           map_size = (size_t)total;
306 1           base = mmap(NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
307 1 50         if (base == MAP_FAILED) { GRAPH_ERR("mmap: %s", strerror(errno)); return NULL; }
    0          
308             } else {
309 2           fd = open(path, O_RDWR|O_CREAT, 0666);
310 2 50         if (fd < 0) { GRAPH_ERR("open: %s", strerror(errno)); return NULL; }
    0          
311 2 50         if (flock(fd, LOCK_EX) < 0) { GRAPH_ERR("flock: %s", strerror(errno)); close(fd); return NULL; }
    0          
312             struct stat st;
313 2 50         if (fstat(fd, &st) < 0) { GRAPH_ERR("fstat: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL; }
    0          
314 2           int is_new = (st.st_size == 0);
315 2 100         if (is_new && ftruncate(fd, (off_t)total) < 0) {
    50          
316 0 0         GRAPH_ERR("ftruncate: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL;
317             }
318 2 100         map_size = is_new ? (size_t)total : (size_t)st.st_size;
319 2           base = mmap(NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
320 2 50         if (base == MAP_FAILED) { GRAPH_ERR("mmap: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL; }
    0          
321 2 100         if (!is_new) {
322 1           GraphHeader *hdr = (GraphHeader *)base;
323 1 50         if (hdr->magic != GRAPH_MAGIC || hdr->version != GRAPH_VERSION ||
    50          
324 1 50         hdr->total_size != (uint64_t)st.st_size) {
325 0 0         GRAPH_ERR("invalid graph file"); munmap(base, map_size); flock(fd, LOCK_UN); close(fd); return NULL;
326             }
327 1           flock(fd, LOCK_UN); close(fd);
328 1           goto setup;
329             }
330             }
331              
332             {
333 2           GraphHeader *hdr = (GraphHeader *)base;
334 2           memset(base, 0, (size_t)total);
335 2           hdr->magic = GRAPH_MAGIC;
336 2           hdr->version = GRAPH_VERSION;
337 2           hdr->max_nodes = max_nodes;
338 2           hdr->max_edges = max_edges;
339 2           hdr->total_size = total;
340 2           hdr->node_data_off = node_data_off;
341 2           hdr->node_heads_off = node_heads_off;
342 2           hdr->node_bitmap_off = node_bitmap_off;
343 2           hdr->edge_data_off = edge_data_off;
344 2           hdr->edge_bitmap_off = edge_bitmap_off;
345             /* init all node heads to GRAPH_NONE */
346 2           uint32_t *heads = (uint32_t *)((uint8_t *)base + node_heads_off);
347 22 100         for (uint32_t i = 0; i < max_nodes; i++) heads[i] = GRAPH_NONE;
348 2           __atomic_thread_fence(__ATOMIC_SEQ_CST);
349             }
350 2 100         if (fd >= 0) { flock(fd, LOCK_UN); close(fd); }
351              
352 1           setup:;
353             {
354 3           GraphHeader *hdr = (GraphHeader *)base;
355 3           GraphHandle *h = (GraphHandle *)calloc(1, sizeof(GraphHandle));
356 3 50         if (!h) { munmap(base, map_size); return NULL; }
357 3           h->hdr = hdr;
358 3           h->node_data = (int64_t *)((uint8_t *)base + hdr->node_data_off);
359 3           h->node_heads = (uint32_t *)((uint8_t *)base + hdr->node_heads_off);
360 3           h->node_bitmap = (uint64_t *)((uint8_t *)base + hdr->node_bitmap_off);
361 3           h->edges = (GraphEdge *)((uint8_t *)base + hdr->edge_data_off);
362 3           h->edge_bitmap = (uint64_t *)((uint8_t *)base + hdr->edge_bitmap_off);
363 3           h->node_bwords = (hdr->max_nodes + 63) / 64;
364 3           h->edge_bwords = (hdr->max_edges + 63) / 64;
365 3           h->mmap_size = map_size;
366 3 100         h->path = path ? strdup(path) : NULL;
367 3           h->notify_fd = -1;
368 3           h->backing_fd = -1;
369 3           return h;
370             }
371             }
372              
373 3           static void graph_destroy(GraphHandle *h) {
374 3 50         if (!h) return;
375 3 50         if (h->notify_fd >= 0) close(h->notify_fd);
376 3 50         if (h->backing_fd >= 0) close(h->backing_fd);
377 3 50         if (h->hdr) munmap(h->hdr, h->mmap_size);
378 3           free(h->path);
379 3           free(h);
380             }
381              
382             #endif /* GRAPH_H */