File Coverage

bitset.h
Criterion Covered Total %
statement 162 170 95.2
branch 82 160 51.2
condition n/a
subroutine n/a
pod n/a
total 244 330 73.9


line stmt bran cond sub pod time code
1             /*
2             * bitset.h -- Shared-memory fixed-size bitset for Linux
3             *
4             * CAS-based atomic per-bit operations on uint64_t words.
5             * Lock-free set/clear/test/toggle with popcount.
6             */
7              
8             #ifndef BITSET_H
9             #define BITSET_H
10              
11             #include
12             #include
13             #include
14             #include
15             #include
16             #include
17             #include
18             #include
19             #include
20             #include
21              
22             #define BS_MAGIC 0x42535431U /* "BST1" */
23             #define BS_VERSION 1
24             #define BS_ERR_BUFLEN 256
25              
26             /* ================================================================
27             * Header (128 bytes)
28             * ================================================================ */
29              
30             typedef struct {
31             uint32_t magic;
32             uint32_t version;
33             uint64_t capacity; /* 8: number of bits */
34             uint64_t total_size; /* 16 */
35             uint64_t data_off; /* 24 */
36             uint32_t num_words; /* 32: ceil(capacity/64) */
37             uint8_t _pad0[28]; /* 36-63 */
38              
39             uint64_t stat_sets; /* 64 */
40             uint64_t stat_clears; /* 72 */
41             uint64_t stat_toggles; /* 80 */
42             uint8_t _pad1[40]; /* 88-127 */
43             } BsHeader;
44              
45             #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
46             _Static_assert(sizeof(BsHeader) == 128, "BsHeader must be 128 bytes");
47             #endif
48              
49             typedef struct {
50             BsHeader *hdr;
51             uint64_t *data;
52             size_t mmap_size;
53             char *path;
54             int backing_fd;
55             } BsHandle;
56              
57             /* ================================================================
58             * Bit operations (lock-free CAS)
59             * ================================================================ */
60              
61 271           static inline int bs_test(BsHandle *h, uint64_t bit) {
62 271           uint64_t word = __atomic_load_n(&h->data[bit / 64], __ATOMIC_ACQUIRE);
63 271           return (word >> (bit % 64)) & 1;
64             }
65              
66             /* Set bit. Returns old value (0 or 1). */
67 87           static inline int bs_set(BsHandle *h, uint64_t bit) {
68 87           uint32_t widx = (uint32_t)(bit / 64);
69 87           uint64_t mask = (uint64_t)1 << (bit % 64);
70 0           for (;;) {
71 87           uint64_t word = __atomic_load_n(&h->data[widx], __ATOMIC_RELAXED);
72 172 100         if (word & mask) return 1; /* already set */
73 85 50         if (__atomic_compare_exchange_n(&h->data[widx], &word, word | mask,
74             1, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)) {
75 85           __atomic_add_fetch(&h->hdr->stat_sets, 1, __ATOMIC_RELAXED);
76 85           return 0;
77             }
78             }
79             }
80              
81             /* Clear bit. Returns old value (0 or 1). */
82 6           static inline int bs_clear(BsHandle *h, uint64_t bit) {
83 6           uint32_t widx = (uint32_t)(bit / 64);
84 6           uint64_t mask = (uint64_t)1 << (bit % 64);
85 0           for (;;) {
86 6           uint64_t word = __atomic_load_n(&h->data[widx], __ATOMIC_RELAXED);
87 10 100         if (!(word & mask)) return 0; /* already clear */
88 4 50         if (__atomic_compare_exchange_n(&h->data[widx], &word, word & ~mask,
89             1, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)) {
90 4           __atomic_add_fetch(&h->hdr->stat_clears, 1, __ATOMIC_RELAXED);
91 4           return 1;
92             }
93             }
94             }
95              
96             /* Toggle bit. Returns new value (0 or 1). */
97 2           static inline int bs_toggle(BsHandle *h, uint64_t bit) {
98 2           uint32_t widx = (uint32_t)(bit / 64);
99 2           uint64_t mask = (uint64_t)1 << (bit % 64);
100 2           uint64_t old = __atomic_fetch_xor(&h->data[widx], mask, __ATOMIC_ACQ_REL);
101 2           __atomic_add_fetch(&h->hdr->stat_toggles, 1, __ATOMIC_RELAXED);
102 2           return (old & mask) ? 0 : 1;
103             }
104              
105             /* Population count — total set bits. */
106 16           static inline uint64_t bs_count(BsHandle *h) {
107 16           uint64_t total = 0;
108 16           uint32_t nw = h->hdr->num_words;
109 62 100         for (uint32_t i = 0; i < nw; i++) {
110 46           uint64_t word = __atomic_load_n(&h->data[i], __ATOMIC_RELAXED);
111 46           total += (uint64_t)__builtin_popcountll(word);
112             }
113 16           return total;
114             }
115              
116 4           static inline int bs_any(BsHandle *h) {
117 4           uint32_t nw = h->hdr->num_words;
118 9 100         for (uint32_t i = 0; i < nw; i++)
119 7 100         if (__atomic_load_n(&h->data[i], __ATOMIC_RELAXED)) return 1;
120 2           return 0;
121             }
122              
123 1           static inline int bs_none(BsHandle *h) { return !bs_any(h); }
124              
125             /* Fill all bits. NOT safe concurrently with per-bit CAS ops. */
126 3           static inline void bs_fill(BsHandle *h) {
127 3           uint32_t nw = h->hdr->num_words;
128 3           uint64_t cap = h->hdr->capacity;
129 10 100         for (uint32_t i = 0; i < nw; i++) {
130 3 100         uint64_t valid = (i == nw - 1 && cap % 64)
131 10 100         ? ((uint64_t)1 << (cap % 64)) - 1 : ~(uint64_t)0;
132 7           __atomic_store_n(&h->data[i], valid, __ATOMIC_RELEASE);
133             }
134 3           }
135              
136             /* Zero all bits. NOT safe concurrently with per-bit CAS ops. */
137 4           static inline void bs_zero(BsHandle *h) {
138 4           uint32_t nw = h->hdr->num_words;
139 20 100         for (uint32_t i = 0; i < nw; i++)
140 16           __atomic_store_n(&h->data[i], 0, __ATOMIC_RELEASE);
141 4           }
142              
143             /* Find first set bit. Returns -1 if none. */
144 3           static inline int64_t bs_first_set(BsHandle *h) {
145 3           uint32_t nw = h->hdr->num_words;
146 3           uint64_t cap = h->hdr->capacity;
147 7 100         for (uint32_t i = 0; i < nw; i++) {
148 6           uint64_t word = __atomic_load_n(&h->data[i], __ATOMIC_RELAXED);
149 6 100         if (word) {
150 2           uint64_t bit = (uint64_t)i * 64 + __builtin_ctzll(word);
151 2 50         return bit < cap ? (int64_t)bit : -1;
152             }
153             }
154 1           return -1;
155             }
156              
157             /* Find first clear bit. Returns -1 if none. */
158 3           static inline int64_t bs_first_clear(BsHandle *h) {
159 3           uint32_t nw = h->hdr->num_words;
160 3           uint64_t cap = h->hdr->capacity;
161 8 100         for (uint32_t i = 0; i < nw; i++) {
162 7           uint64_t word = __atomic_load_n(&h->data[i], __ATOMIC_RELAXED);
163 7 100         if (word != ~(uint64_t)0) {
164 2           uint64_t bit = (uint64_t)i * 64 + __builtin_ctzll(~word);
165 2 100         return bit < cap ? (int64_t)bit : -1;
166             }
167             }
168 1           return -1;
169             }
170              
171             /* ================================================================
172             * Create / Open / Close
173             * ================================================================ */
174              
175             #define BS_ERR(fmt, ...) do { if (errbuf) snprintf(errbuf, BS_ERR_BUFLEN, fmt, ##__VA_ARGS__); } while(0)
176              
177 11           static inline void bs_init_header(void *base, uint64_t total, uint64_t capacity, uint32_t nw) {
178 11           BsHeader *hdr = (BsHeader *)base;
179 11           memset(base, 0, (size_t)total);
180 11           hdr->magic = BS_MAGIC;
181 11           hdr->version = BS_VERSION;
182 11           hdr->capacity = capacity;
183 11           hdr->total_size = total;
184 11           hdr->data_off = sizeof(BsHeader);
185 11           hdr->num_words = nw;
186 11           __atomic_thread_fence(__ATOMIC_SEQ_CST);
187 11           }
188              
189             /* Validate a mapped header (shared by bs_create reopen and bs_open_fd). */
190 2           static inline int bs_validate_header(const BsHeader *hdr, uint64_t file_size) {
191 2 50         if (hdr->magic != BS_MAGIC) return 0;
192 2 50         if (hdr->version != BS_VERSION) return 0;
193 2 50         if (hdr->capacity == 0) return 0;
194 2 50         if (hdr->capacity > (uint64_t)(UINT32_MAX - 63) * 64) return 0;
195 2 50         if (hdr->total_size != file_size) return 0;
196 2 50         if (hdr->data_off != sizeof(BsHeader)) return 0;
197 2           uint32_t exp_nw = (uint32_t)((hdr->capacity + 63) / 64);
198 2 50         if (hdr->num_words != exp_nw) return 0;
199 2           uint64_t exp_total = sizeof(BsHeader) + (uint64_t)exp_nw * 8;
200 2 50         if (hdr->total_size != exp_total) return 0;
201 2           return 1;
202             }
203              
204 13           static inline BsHandle *bs_setup(void *base, size_t ms, const char *path, int bfd) {
205 13           BsHeader *hdr = (BsHeader *)base;
206 13           BsHandle *h = (BsHandle *)calloc(1, sizeof(BsHandle));
207 13 50         if (!h) { munmap(base, ms); return NULL; }
208 13           h->hdr = hdr;
209 13           h->data = (uint64_t *)((uint8_t *)base + hdr->data_off);
210 13           h->mmap_size = ms;
211 13 100         h->path = path ? strdup(path) : NULL;
212 13           h->backing_fd = bfd;
213 13           return h;
214             }
215              
216 5           static BsHandle *bs_create(const char *path, uint64_t capacity, char *errbuf) {
217 5 50         if (errbuf) errbuf[0] = '\0';
218 5 50         if (capacity == 0) { BS_ERR("capacity must be > 0"); return NULL; }
    0          
219 5           uint32_t nw = (uint32_t)((capacity + 63) / 64);
220 5 50         if (capacity > (uint64_t)(UINT32_MAX - 63) * 64) { BS_ERR("capacity too large"); return NULL; }
    0          
221 5           uint64_t total = sizeof(BsHeader) + (uint64_t)nw * 8;
222              
223 5           int anonymous = (path == NULL);
224 5           int fd = -1;
225             size_t map_size;
226             void *base;
227              
228 5 100         if (anonymous) {
229 3           map_size = (size_t)total;
230 3           base = mmap(NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
231 3 50         if (base == MAP_FAILED) { BS_ERR("mmap: %s", strerror(errno)); return NULL; }
    0          
232             } else {
233 2           fd = open(path, O_RDWR|O_CREAT, 0666);
234 3 50         if (fd < 0) { BS_ERR("open: %s", strerror(errno)); return NULL; }
    0          
235 2 50         if (flock(fd, LOCK_EX) < 0) { BS_ERR("flock: %s", strerror(errno)); close(fd); return NULL; }
    0          
236             struct stat st;
237 2 50         if (fstat(fd, &st) < 0) { BS_ERR("fstat: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL; }
    0          
238 2           int is_new = (st.st_size == 0);
239 2 100         if (!is_new && (uint64_t)st.st_size < sizeof(BsHeader)) {
    50          
240 0 0         BS_ERR("%s: file too small (%lld)", path, (long long)st.st_size);
241 0           flock(fd, LOCK_UN); close(fd); return NULL;
242             }
243 2 100         if (is_new && ftruncate(fd, (off_t)total) < 0) {
    50          
244 0 0         BS_ERR("ftruncate: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL;
245             }
246 2 100         map_size = is_new ? (size_t)total : (size_t)st.st_size;
247 2           base = mmap(NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
248 2 50         if (base == MAP_FAILED) { BS_ERR("mmap: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL; }
    0          
249 2 100         if (!is_new) {
250 1 50         if (!bs_validate_header((BsHeader *)base, (uint64_t)st.st_size)) {
251 0 0         BS_ERR("invalid bitset file"); munmap(base, map_size); flock(fd, LOCK_UN); close(fd); return NULL;
252             }
253 1           flock(fd, LOCK_UN); close(fd);
254 1           return bs_setup(base, map_size, path, -1);
255             }
256             }
257 4           bs_init_header(base, total, capacity, nw);
258 4 100         if (fd >= 0) { flock(fd, LOCK_UN); close(fd); }
259 4           return bs_setup(base, map_size, path, -1);
260             }
261              
262 7           static BsHandle *bs_create_memfd(const char *name, uint64_t capacity, char *errbuf) {
263 7 50         if (errbuf) errbuf[0] = '\0';
264 7 50         if (capacity == 0) { BS_ERR("capacity must be > 0"); return NULL; }
    0          
265 7           uint32_t nw = (uint32_t)((capacity + 63) / 64);
266 7 50         if (capacity > (uint64_t)(UINT32_MAX - 63) * 64) { BS_ERR("capacity too large"); return NULL; }
    0          
267 7           uint64_t total = sizeof(BsHeader) + (uint64_t)nw * 8;
268 7 50         int fd = memfd_create(name ? name : "bitset", MFD_CLOEXEC | MFD_ALLOW_SEALING);
269 7 50         if (fd < 0) { BS_ERR("memfd_create: %s", strerror(errno)); return NULL; }
    0          
270 7 50         if (ftruncate(fd, (off_t)total) < 0) { BS_ERR("ftruncate: %s", strerror(errno)); close(fd); return NULL; }
    0          
271 7           (void)fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW);
272 7           void *base = mmap(NULL, (size_t)total, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
273 7 50         if (base == MAP_FAILED) { BS_ERR("mmap: %s", strerror(errno)); close(fd); return NULL; }
    0          
274 7           bs_init_header(base, total, capacity, nw);
275 7           return bs_setup(base, (size_t)total, NULL, fd);
276             }
277              
278 1           static BsHandle *bs_open_fd(int fd, char *errbuf) {
279 1 50         if (errbuf) errbuf[0] = '\0';
280             struct stat st;
281 1 50         if (fstat(fd, &st) < 0) { BS_ERR("fstat: %s", strerror(errno)); return NULL; }
    0          
282 1 50         if ((uint64_t)st.st_size < sizeof(BsHeader)) { BS_ERR("too small"); return NULL; }
    0          
283 1           size_t ms = (size_t)st.st_size;
284 1           void *base = mmap(NULL, ms, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
285 1 50         if (base == MAP_FAILED) { BS_ERR("mmap: %s", strerror(errno)); return NULL; }
    0          
286 1 50         if (!bs_validate_header((BsHeader *)base, (uint64_t)st.st_size)) {
287 0 0         BS_ERR("invalid bitset"); munmap(base, ms); return NULL;
288             }
289 1           int myfd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
290 1 50         if (myfd < 0) { BS_ERR("fcntl: %s", strerror(errno)); munmap(base, ms); return NULL; }
    0          
291 1           return bs_setup(base, ms, NULL, myfd);
292             }
293              
294 13           static void bs_destroy(BsHandle *h) {
295 13 50         if (!h) return;
296 13 100         if (h->backing_fd >= 0) close(h->backing_fd);
297 13 50         if (h->hdr) munmap(h->hdr, h->mmap_size);
298 13           free(h->path);
299 13           free(h);
300             }
301              
302 0           static int bs_msync(BsHandle *h) { return msync(h->hdr, h->mmap_size, MS_SYNC); }
303              
304             #endif /* BITSET_H */