File Coverage

bitset.h
Criterion Covered Total %
statement 152 158 96.2
branch 75 146 51.3
condition n/a
subroutine n/a
pod n/a
total 227 304 74.6


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 263           static inline int bs_test(BsHandle *h, uint64_t bit) {
62 263           uint64_t word = __atomic_load_n(&h->data[bit / 64], __ATOMIC_ACQUIRE);
63 263           return (word >> (bit % 64)) & 1;
64             }
65              
66             /* Set bit. Returns old value (0 or 1). */
67 18           static inline int bs_set(BsHandle *h, uint64_t bit) {
68 18           uint32_t widx = (uint32_t)(bit / 64);
69 18           uint64_t mask = (uint64_t)1 << (bit % 64);
70 0           for (;;) {
71 18           uint64_t word = __atomic_load_n(&h->data[widx], __ATOMIC_RELAXED);
72 35 100         if (word & mask) return 1; /* already set */
73 17 50         if (__atomic_compare_exchange_n(&h->data[widx], &word, word | mask,
74             1, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)) {
75 17           __atomic_add_fetch(&h->hdr->stat_sets, 1, __ATOMIC_RELAXED);
76 17           return 0;
77             }
78             }
79             }
80              
81             /* Clear bit. Returns old value (0 or 1). */
82 2           static inline int bs_clear(BsHandle *h, uint64_t bit) {
83 2           uint32_t widx = (uint32_t)(bit / 64);
84 2           uint64_t mask = (uint64_t)1 << (bit % 64);
85 0           for (;;) {
86 2           uint64_t word = __atomic_load_n(&h->data[widx], __ATOMIC_RELAXED);
87 3 100         if (!(word & mask)) return 0; /* already clear */
88 1 50         if (__atomic_compare_exchange_n(&h->data[widx], &word, word & ~mask,
89             1, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)) {
90 1           __atomic_add_fetch(&h->hdr->stat_clears, 1, __ATOMIC_RELAXED);
91 1           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 11           static inline uint64_t bs_count(BsHandle *h) {
107 11           uint64_t total = 0;
108 11           uint32_t nw = h->hdr->num_words;
109 45 100         for (uint32_t i = 0; i < nw; i++) {
110 34           uint64_t word = __atomic_load_n(&h->data[i], __ATOMIC_RELAXED);
111 34           total += (uint64_t)__builtin_popcountll(word);
112             }
113 11           return total;
114             }
115              
116 2           static inline int bs_any(BsHandle *h) {
117 2           uint32_t nw = h->hdr->num_words;
118 6 100         for (uint32_t i = 0; i < nw; i++)
119 5 100         if (__atomic_load_n(&h->data[i], __ATOMIC_RELAXED)) return 1;
120 1           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 5           static inline void bs_init_header(void *base, uint64_t total, uint64_t capacity, uint32_t nw) {
178 5           BsHeader *hdr = (BsHeader *)base;
179 5           memset(base, 0, (size_t)total);
180 5           hdr->magic = BS_MAGIC;
181 5           hdr->version = BS_VERSION;
182 5           hdr->capacity = capacity;
183 5           hdr->total_size = total;
184 5           hdr->data_off = sizeof(BsHeader);
185 5           hdr->num_words = nw;
186 5           __atomic_thread_fence(__ATOMIC_SEQ_CST);
187 5           }
188              
189 7           static inline BsHandle *bs_setup(void *base, size_t ms, const char *path, int bfd) {
190 7           BsHeader *hdr = (BsHeader *)base;
191 7           BsHandle *h = (BsHandle *)calloc(1, sizeof(BsHandle));
192 7 50         if (!h) { munmap(base, ms); return NULL; }
193 7           h->hdr = hdr;
194 7           h->data = (uint64_t *)((uint8_t *)base + hdr->data_off);
195 7           h->mmap_size = ms;
196 7 100         h->path = path ? strdup(path) : NULL;
197 7           h->backing_fd = bfd;
198 7           return h;
199             }
200              
201 5           static BsHandle *bs_create(const char *path, uint64_t capacity, char *errbuf) {
202 5 50         if (errbuf) errbuf[0] = '\0';
203 5 50         if (capacity == 0) { BS_ERR("capacity must be > 0"); return NULL; }
    0          
204 5           uint32_t nw = (uint32_t)((capacity + 63) / 64);
205 5 50         if (capacity > (uint64_t)(UINT32_MAX - 63) * 64) { BS_ERR("capacity too large"); return NULL; }
    0          
206 5           uint64_t total = sizeof(BsHeader) + (uint64_t)nw * 8;
207              
208 5           int anonymous = (path == NULL);
209 5           int fd = -1;
210             size_t map_size;
211             void *base;
212              
213 5 100         if (anonymous) {
214 3           map_size = (size_t)total;
215 3           base = mmap(NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
216 3 50         if (base == MAP_FAILED) { BS_ERR("mmap: %s", strerror(errno)); return NULL; }
    0          
217             } else {
218 2           fd = open(path, O_RDWR|O_CREAT, 0666);
219 3 50         if (fd < 0) { BS_ERR("open: %s", strerror(errno)); return NULL; }
    0          
220 2 50         if (flock(fd, LOCK_EX) < 0) { BS_ERR("flock: %s", strerror(errno)); close(fd); return NULL; }
    0          
221             struct stat st;
222 2 50         if (fstat(fd, &st) < 0) { BS_ERR("fstat: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL; }
    0          
223 2           int is_new = (st.st_size == 0);
224 2 100         if (is_new && ftruncate(fd, (off_t)total) < 0) {
    50          
225 0 0         BS_ERR("ftruncate: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL;
226             }
227 2 100         map_size = is_new ? (size_t)total : (size_t)st.st_size;
228 2           base = mmap(NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
229 2 50         if (base == MAP_FAILED) { BS_ERR("mmap: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL; }
    0          
230 2 100         if (!is_new) {
231 1           BsHeader *hdr = (BsHeader *)base;
232 1 50         if (hdr->magic != BS_MAGIC || hdr->version != BS_VERSION ||
    50          
233 1 50         hdr->total_size != (uint64_t)st.st_size) {
234 0 0         BS_ERR("invalid bitset file"); munmap(base, map_size); flock(fd, LOCK_UN); close(fd); return NULL;
235             }
236 1           flock(fd, LOCK_UN); close(fd);
237 1           return bs_setup(base, map_size, path, -1);
238             }
239             }
240 4           bs_init_header(base, total, capacity, nw);
241 4 100         if (fd >= 0) { flock(fd, LOCK_UN); close(fd); }
242 4           return bs_setup(base, map_size, path, -1);
243             }
244              
245 1           static BsHandle *bs_create_memfd(const char *name, uint64_t capacity, char *errbuf) {
246 1 50         if (errbuf) errbuf[0] = '\0';
247 1 50         if (capacity == 0) { BS_ERR("capacity must be > 0"); return NULL; }
    0          
248 1           uint32_t nw = (uint32_t)((capacity + 63) / 64);
249 1 50         if (capacity > (uint64_t)(UINT32_MAX - 63) * 64) { BS_ERR("capacity too large"); return NULL; }
    0          
250 1           uint64_t total = sizeof(BsHeader) + (uint64_t)nw * 8;
251 1 50         int fd = memfd_create(name ? name : "bitset", MFD_CLOEXEC);
252 1 50         if (fd < 0) { BS_ERR("memfd_create: %s", strerror(errno)); return NULL; }
    0          
253 1 50         if (ftruncate(fd, (off_t)total) < 0) { BS_ERR("ftruncate: %s", strerror(errno)); close(fd); return NULL; }
    0          
254 1           void *base = mmap(NULL, (size_t)total, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
255 1 50         if (base == MAP_FAILED) { BS_ERR("mmap: %s", strerror(errno)); close(fd); return NULL; }
    0          
256 1           bs_init_header(base, total, capacity, nw);
257 1           return bs_setup(base, (size_t)total, NULL, fd);
258             }
259              
260 1           static BsHandle *bs_open_fd(int fd, char *errbuf) {
261 1 50         if (errbuf) errbuf[0] = '\0';
262             struct stat st;
263 1 50         if (fstat(fd, &st) < 0) { BS_ERR("fstat: %s", strerror(errno)); return NULL; }
    0          
264 1 50         if ((uint64_t)st.st_size < sizeof(BsHeader)) { BS_ERR("too small"); return NULL; }
    0          
265 1           size_t ms = (size_t)st.st_size;
266 1           void *base = mmap(NULL, ms, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
267 1 50         if (base == MAP_FAILED) { BS_ERR("mmap: %s", strerror(errno)); return NULL; }
    0          
268 1           BsHeader *hdr = (BsHeader *)base;
269 1 50         if (hdr->magic != BS_MAGIC || hdr->version != BS_VERSION ||
    50          
270 1 50         hdr->total_size != (uint64_t)st.st_size) {
271 0 0         BS_ERR("invalid bitset"); munmap(base, ms); return NULL;
272             }
273 1           int myfd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
274 1 50         if (myfd < 0) { BS_ERR("fcntl: %s", strerror(errno)); munmap(base, ms); return NULL; }
    0          
275 1           return bs_setup(base, ms, NULL, myfd);
276             }
277              
278 7           static void bs_destroy(BsHandle *h) {
279 7 50         if (!h) return;
280 7 100         if (h->backing_fd >= 0) close(h->backing_fd);
281 7 50         if (h->hdr) munmap(h->hdr, h->mmap_size);
282 7           free(h->path);
283 7           free(h);
284             }
285              
286 0           static void bs_msync(BsHandle *h) { msync(h->hdr, h->mmap_size, MS_SYNC); }
287              
288             #endif /* BITSET_H */