File Coverage

Shared.xs
Criterion Covered Total %
statement 184 187 98.4
branch 148 220 67.2
condition n/a
subroutine n/a
pod n/a
total 332 407 81.5


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT
2             #include "EXTERN.h"
3             #include "perl.h"
4             #include "XSUB.h"
5             #include "ppport.h"
6             #include "roaring.h"
7              
8             #define EXTRACT(sv) \
9             if (!sv_isobject(sv) || !sv_derived_from(sv, "Data::RoaringBitmap::Shared")) \
10             croak("Expected a Data::RoaringBitmap::Shared object"); \
11             RbHandle *h = INT2PTR(RbHandle*, SvIV(SvRV(sv))); \
12             if (!h) croak("Attempted to use a destroyed Data::RoaringBitmap::Shared object")
13              
14             #define MAKE_OBJ(class, handle) \
15             SV *obj = newSViv(PTR2IV(handle)); \
16             SV *ref = newRV_noinc(obj); \
17             sv_bless(ref, gv_stashpv(class, GV_ADD)); \
18             RETVAL = ref
19              
20             #define RB_UINT32_MAX_UV ((UV)0xFFFFFFFF)
21              
22             /* Acquire the receiver `a`'s WRITE lock and the other `b`'s READ lock in a
23             * globally-consistent order keyed on each bitmap's shared-memory identity
24             * (hdr->bitmap_id), NOT the process-local handle pointer. Two unrelated
25             * processes mapping the same pair X,Y therefore agree on order, so a
26             * concurrent X.union(Y) / Y.union(X) cannot deadlock. The receiver always
27             * gets the write lock and the other the read lock regardless of which is
28             * acquired first. a and b must refer to DISTINCT underlying bitmaps (the
29             * caller has already ruled out a == b and equal bitmap_id). */
30 12           static void rb_lock_pair(RbHandle *a, RbHandle *b) {
31 12 100         if (a->hdr->bitmap_id < b->hdr->bitmap_id) {
32 6           rb_rwlock_wrlock(a);
33 6           rb_rwlock_rdlock(b);
34             } else {
35 6           rb_rwlock_rdlock(b);
36 6           rb_rwlock_wrlock(a);
37             }
38 12           }
39 12           static void rb_unlock_pair(RbHandle *a, RbHandle *b) {
40 12 100         if (a->hdr->bitmap_id < b->hdr->bitmap_id) {
41 6           rb_rwlock_rdunlock(b);
42 6           rb_rwlock_wrunlock(a);
43             } else {
44 6           rb_rwlock_wrunlock(a);
45 6           rb_rwlock_rdunlock(b);
46             }
47 12           }
48              
49             MODULE = Data::RoaringBitmap::Shared PACKAGE = Data::RoaringBitmap::Shared
50              
51             PROTOTYPES: DISABLE
52              
53             SV *
54             new(class, path = &PL_sv_undef, container_capacity = 256)
55             const char *class
56             SV *path
57             UV container_capacity
58             PREINIT:
59             char errbuf[RB_ERR_BUFLEN];
60             CODE:
61 43 100         const char *p = SvOK(path) ? SvPV_nolen(path) : NULL;
62 43           RbHandle *h = rb_create(p, (uint64_t)container_capacity, errbuf); /* validates args into errbuf */
63 43 100         if (!h) croak("Data::RoaringBitmap::Shared->new: %s", errbuf);
64 40           MAKE_OBJ(class, h);
65             OUTPUT:
66             RETVAL
67              
68             SV *
69             new_memfd(class, name = &PL_sv_undef, container_capacity = 256)
70             const char *class
71             SV *name
72             UV container_capacity
73             PREINIT:
74             char errbuf[RB_ERR_BUFLEN];
75             CODE:
76 3 100         const char *nm = SvOK(name) ? SvPV_nolen(name) : NULL; /* undef -> default label */
77 3           RbHandle *h = rb_create_memfd(nm, (uint64_t)container_capacity, errbuf); /* validates args into errbuf */
78 3 100         if (!h) croak("Data::RoaringBitmap::Shared->new_memfd: %s", errbuf);
79 2           MAKE_OBJ(class, h);
80             OUTPUT:
81             RETVAL
82              
83             SV *
84             new_from_fd(class, fd)
85             const char *class
86             int fd
87             PREINIT:
88             char errbuf[RB_ERR_BUFLEN];
89             CODE:
90 2           RbHandle *h = rb_open_fd(fd, errbuf);
91 2 100         if (!h) croak("Data::RoaringBitmap::Shared->new_from_fd: %s", errbuf);
92 1           MAKE_OBJ(class, h);
93             OUTPUT:
94             RETVAL
95              
96             void
97             DESTROY(self)
98             SV *self
99             CODE:
100 45 50         if (sv_isobject(self) && sv_derived_from(self, "Data::RoaringBitmap::Shared")) {
    50          
101 45           RbHandle *h = INT2PTR(RbHandle*, SvIV(SvRV(self)));
102 45 100         if (h) { sv_setiv(SvRV(self), 0); rb_destroy(h); } /* null first: activates EXTRACT's use-after-destroy croak + makes a double DESTROY a no-op */
103             }
104              
105             IV
106             add(self, x)
107             SV *self
108             UV x
109             PREINIT:
110 44630 50         EXTRACT(self);
    50          
    50          
111             int added;
112             uint32_t hi;
113             CODE:
114             /* Range-check BEFORE locking: a croak must never happen under the lock. */
115 44630 100         if (x > RB_UINT32_MAX_UV)
116 2           croak("Data::RoaringBitmap::Shared->add: value %" UVuf " exceeds uint32 (max 4294967295)", x);
117 44628           hi = (uint32_t)(x >> 16);
118 44628           rb_rwlock_wrlock(h);
119             /* Need a free container slot only if this value touches an empty bucket. */
120 44628 100         if (rb_buckets(h)[hi].type == RB_TYPE_NONE && rb_avail_slots(h) == 0) {
    100          
121 2           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED); /* a call that took the write lock counts (matches add_many) */
122 2           rb_rwlock_wrunlock(h); /* release BEFORE croak */
123 2           croak("Data::RoaringBitmap::Shared->add: container pool exhausted "
124             "(grow container_capacity)");
125             }
126 44626           added = rb_add_locked(h, (uint32_t)x);
127 44626           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
128 44626           rb_rwlock_wrunlock(h);
129 44626 100         RETVAL = (IV)added;
130             OUTPUT:
131             RETVAL
132              
133             IV
134             add_many(self, ints)
135             SV *self
136             SV *ints
137             PREINIT:
138 21 50         EXTRACT(self);
    50          
    50          
139             AV *av;
140             SSize_t n, i;
141             UV *vals;
142             IV total_added;
143             CODE:
144 21 50         if (!SvROK(ints) || SvTYPE(SvRV(ints)) != SVt_PVAV)
    50          
145 0           croak("Data::RoaringBitmap::Shared->add_many: expected an array reference");
146 21           av = (AV *)SvRV(ints);
147 21           n = av_len(av) + 1;
148             /* Resolve + range-check every value BEFORE the lock (croak-free section). */
149 21 50         Newx(vals, n > 0 ? n : 1, UV);
    50          
    50          
150 21           SAVEFREEPV(vals);
151 39097 100         for (i = 0; i < n; i++) {
152 39077           SV **e = av_fetch(av, i, 0);
153 39077 50         UV v = (e && SvOK(*e)) ? SvUV(*e) : 0;
    50          
154 39077 100         if (v > RB_UINT32_MAX_UV) {
155 1           croak("Data::RoaringBitmap::Shared->add_many: value %" UVuf
156             " exceeds uint32 (max 4294967295)", v);
157             }
158 39076           vals[i] = v;
159             }
160 20           total_added = 0;
161 20           rb_rwlock_wrlock(h);
162 39092 100         for (i = 0; i < n; i++) {
163 39073           uint32_t hi = (uint32_t)(vals[i] >> 16);
164 39073 100         if (rb_buckets(h)[hi].type == RB_TYPE_NONE && rb_avail_slots(h) == 0) {
    100          
165 1           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
166 1           rb_rwlock_wrunlock(h); /* release BEFORE croak; partial adds remain (documented non-atomic) */
167 1           croak("Data::RoaringBitmap::Shared->add_many: container pool exhausted "
168             "after adding %" IVdf " element(s) (grow container_capacity)", total_added);
169             }
170 39072           total_added += rb_add_locked(h, (uint32_t)vals[i]);
171             }
172 19           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
173 19           rb_rwlock_wrunlock(h);
174 19 100         RETVAL = total_added;
175             OUTPUT:
176             RETVAL
177              
178             bool
179             contains(self, x)
180             SV *self
181             UV x
182             PREINIT:
183 12786 50         EXTRACT(self);
    50          
    100          
184             CODE:
185 12785 100         if (x > RB_UINT32_MAX_UV) { RETVAL = 0; } /* a value out of range is simply absent */
186             else {
187 12784           rb_rwlock_rdlock(h);
188 12784           RETVAL = rb_contains_locked(h, (uint32_t)x) ? 1 : 0;
189 12784           rb_rwlock_rdunlock(h);
190             }
191             OUTPUT:
192             RETVAL
193              
194             IV
195             remove(self, x)
196             SV *self
197             UV x
198             PREINIT:
199 5005 50         EXTRACT(self);
    50          
    50          
200             int removed;
201             CODE:
202 5005 100         if (x > RB_UINT32_MAX_UV) { RETVAL = 0; } /* out of range -> nothing to remove */
203             else {
204 5004           rb_rwlock_wrlock(h);
205 5004           removed = rb_remove_locked(h, (uint32_t)x);
206 5004           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
207 5004           rb_rwlock_wrunlock(h);
208 5004           RETVAL = (IV)removed;
209             }
210             OUTPUT:
211             RETVAL
212              
213             UV
214             cardinality(self)
215             SV *self
216             PREINIT:
217 37 50         EXTRACT(self);
    50          
    50          
218             CODE:
219 37           rb_rwlock_rdlock(h);
220 37           RETVAL = (UV)h->hdr->cardinality;
221 37           rb_rwlock_rdunlock(h);
222             OUTPUT:
223             RETVAL
224              
225             bool
226             is_empty(self)
227             SV *self
228             PREINIT:
229 3 50         EXTRACT(self);
    50          
    50          
230             CODE:
231 3           rb_rwlock_rdlock(h);
232 3           RETVAL = (h->hdr->cardinality == 0) ? 1 : 0;
233 3           rb_rwlock_rdunlock(h);
234             OUTPUT:
235             RETVAL
236              
237             SV *
238             min(self)
239             SV *self
240             PREINIT:
241 8 50         EXTRACT(self);
    50          
    50          
242             uint32_t v;
243             int found;
244             CODE:
245 8           rb_rwlock_rdlock(h);
246 8           found = rb_min_locked(h, &v);
247 8           rb_rwlock_rdunlock(h);
248 8 100         RETVAL = found ? newSVuv((UV)v) : &PL_sv_undef;
249             OUTPUT:
250             RETVAL
251              
252             SV *
253             max(self)
254             SV *self
255             PREINIT:
256 8 50         EXTRACT(self);
    50          
    50          
257             uint32_t v;
258             int found;
259             CODE:
260 8           rb_rwlock_rdlock(h);
261 8           found = rb_max_locked(h, &v);
262 8           rb_rwlock_rdunlock(h);
263 8 100         RETVAL = found ? newSVuv((UV)v) : &PL_sv_undef;
264             OUTPUT:
265             RETVAL
266              
267             SV *
268             to_array(self)
269             SV *self
270             PREINIT:
271 20 50         EXTRACT(self);
    50          
    50          
272             AV *av;
273             UV card;
274             CODE:
275             /* Pre-size the AV to the cardinality BEFORE the lock (av_extend can croak
276             on OOM; av_store afterward cannot). The cardinality may grow between
277             this read and the fill, but to_array is a best-effort snapshot; we cap
278             the fill at the pre-sized length so we never store past the extent. */
279 20           rb_rwlock_rdlock(h);
280 20           card = (UV)h->hdr->cardinality;
281 20           rb_rwlock_rdunlock(h);
282              
283 20           av = newAV();
284 20 100         if (card) av_extend(av, (SSize_t)card - 1); /* room for indices 0..card-1 */
285              
286             {
287             RbBucket *bt;
288 20           UV idx = 0;
289 20           rb_rwlock_rdlock(h);
290 20           bt = rb_buckets(h);
291 395 50         for (uint32_t hi = 0; hi < RB_NUM_BUCKETS && idx < card; hi++) {
    100          
292 375 100         if (bt[hi].type == RB_TYPE_NONE) continue;
293 368 100         if (bt[hi].type == RB_TYPE_ARRAY) {
294 360           uint16_t *vals = rb_array(h, bt[hi].container_off);
295 15976 100         for (uint32_t i = 0; i < bt[hi].cardinality && idx < card; i++)
    50          
296 15616           av_store(av, (SSize_t)idx++, newSVuv(((UV)hi << 16) | vals[i]));
297             } else {
298 8           uint64_t *bits = rb_bitmap(h, bt[hi].container_off);
299 5473 100         for (uint32_t w = 0; w < 1024 && idx < card; w++) {
    100          
300 5465           uint64_t word = bits[w];
301 48511 100         while (word && idx < card) {
    50          
302 43046           uint32_t lo = (w << 6) + (uint32_t)__builtin_ctzll(word);
303 43046           av_store(av, (SSize_t)idx++, newSVuv(((UV)hi << 16) | lo));
304 43046           word &= word - 1; /* clear lowest set bit */
305             }
306             }
307             }
308             }
309 20           rb_rwlock_rdunlock(h);
310             }
311 20           RETVAL = newRV_noinc((SV *)av);
312             OUTPUT:
313             RETVAL
314              
315             SV *
316             union(self, other)
317             SV *self
318             SV *other
319             PREINIT:
320 11 50         EXTRACT(self);
    50          
    50          
321             CODE:
322 11 50         if (!sv_isobject(other) || !sv_derived_from(other, "Data::RoaringBitmap::Shared"))
    50          
323 0           croak("Data::RoaringBitmap::Shared->union: expected a Data::RoaringBitmap::Shared object");
324             {
325 11           RbHandle *o = INT2PTR(RbHandle*, SvIV(SvRV(other)));
326 11 50         if (!o) croak("Attempted to use a destroyed Data::RoaringBitmap::Shared object");
327             /* Same underlying bitmap (same handle, or two handles to one mapping
328             * sharing a bitmap_id) -> a |= a is a no-op. Must short-circuit before
329             * locking: taking the write lock and the read lock on the SAME rwlock
330             * would self-deadlock. */
331 11 100         if (o == h || o->hdr->bitmap_id == h->hdr->bitmap_id) {
    100          
332 4           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
333 4           SvREFCNT_inc(self);
334 4           RETVAL = self;
335             } else {
336             uint32_t need;
337 7           rb_lock_pair(h, o);
338 7           need = rb_union_new_slots_needed(h, o);
339 7 100         if (rb_avail_slots(h) < need) {
340 1           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
341 1           rb_unlock_pair(h, o); /* release BEFORE croak */
342 1           croak("Data::RoaringBitmap::Shared->union: container pool exhausted "
343             "(needs %u more container(s); grow container_capacity)", need);
344             }
345 6           rb_union_locked(h, o);
346 6           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
347 6           rb_unlock_pair(h, o);
348 6           SvREFCNT_inc(self); /* return the receiver for chaining */
349 6           RETVAL = self;
350             }
351             }
352             OUTPUT:
353             RETVAL
354              
355             SV *
356             intersect(self, other)
357             SV *self
358             SV *other
359             PREINIT:
360 8 50         EXTRACT(self);
    50          
    50          
361             CODE:
362 8 50         if (!sv_isobject(other) || !sv_derived_from(other, "Data::RoaringBitmap::Shared"))
    50          
363 0           croak("Data::RoaringBitmap::Shared->intersect: expected a Data::RoaringBitmap::Shared object");
364             {
365 8           RbHandle *o = INT2PTR(RbHandle*, SvIV(SvRV(other)));
366 8 50         if (!o) croak("Attempted to use a destroyed Data::RoaringBitmap::Shared object");
367             /* Same underlying bitmap (same handle, or two handles to one mapping
368             * sharing a bitmap_id) -> a &= a is a no-op. Short-circuit before
369             * locking to avoid self-deadlocking on the one shared rwlock. */
370 8 100         if (o == h || o->hdr->bitmap_id == h->hdr->bitmap_id) {
    100          
371 3           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
372 3           SvREFCNT_inc(self);
373 3           RETVAL = self;
374             } else {
375 5           rb_lock_pair(h, o); /* intersect never needs new slots */
376 5           rb_intersect_locked(h, o);
377 5           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
378 5           rb_unlock_pair(h, o);
379 5           SvREFCNT_inc(self);
380 5           RETVAL = self;
381             }
382             }
383             OUTPUT:
384             RETVAL
385              
386             void
387             clear(self)
388             SV *self
389             PREINIT:
390 1 50         EXTRACT(self);
    50          
    50          
391             CODE:
392 1           rb_rwlock_wrlock(h);
393 1           rb_clear_locked(h);
394 1           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
395 1           rb_rwlock_wrunlock(h);
396              
397             SV *
398             stats(self)
399             SV *self
400             PREINIT:
401 12 50         EXTRACT(self);
    50          
    50          
402             CODE:
403             {
404             uint64_t card, ops;
405             uint32_t cont_used, cont_cap, bkts_used;
406             /* Snapshot under the lock; do all (croak-capable) Perl allocation after
407             releasing it -- so an OOM in newHV/newSV* can never strand the lock. */
408 12           rb_rwlock_rdlock(h);
409 12           card = h->hdr->cardinality;
410 12           cont_used = h->hdr->container_used;
411 12           cont_cap = h->hdr->container_cap;
412 12           ops = h->hdr->stat_ops;
413 12           bkts_used = rb_buckets_used(h);
414 12           rb_rwlock_rdunlock(h);
415              
416 12           HV *hv = newHV();
417 12           hv_stores(hv, "cardinality", newSVuv((UV)card));
418 12           hv_stores(hv, "containers_used", newSVuv((UV)cont_used));
419 12           hv_stores(hv, "containers_capacity", newSVuv((UV)cont_cap));
420 12           hv_stores(hv, "buckets_used", newSVuv((UV)bkts_used));
421 12           hv_stores(hv, "ops", newSVuv((UV)ops));
422 12           hv_stores(hv, "mmap_size", newSVuv((UV)h->mmap_size));
423 12           RETVAL = newRV_noinc((SV *)hv);
424             }
425             OUTPUT:
426             RETVAL
427              
428             SV *
429             path(self)
430             SV *self
431             PREINIT:
432 3 50         EXTRACT(self);
    50          
    50          
433             CODE:
434 3 100         RETVAL = h->path ? newSVpv(h->path, 0) : &PL_sv_undef;
435             OUTPUT:
436             RETVAL
437              
438             int
439             memfd(self)
440             SV *self
441             PREINIT:
442 3 50         EXTRACT(self);
    50          
    50          
443             CODE:
444 3 50         RETVAL = h->backing_fd;
445             OUTPUT:
446             RETVAL
447              
448             void
449             sync(self)
450             SV *self
451             PREINIT:
452 2 50         EXTRACT(self);
    50          
    50          
453             CODE:
454 2 50         if (rb_msync(h) != 0) croak("sync: %s", strerror(errno));
455              
456             void
457             unlink(self, ...)
458             SV *self
459             CODE:
460 3 100         if (sv_isobject(self) && sv_derived_from(self, "Data::RoaringBitmap::Shared")) {
    50          
461 1           RbHandle *h = INT2PTR(RbHandle*, SvIV(SvRV(self)));
462 1 50         if (h && h->path) unlink(h->path);
    50          
463 1 50         } else if (items >= 2 && SvOK(ST(1))) {
    50          
464 1           unlink(SvPV_nolen(ST(1)));
465             }