Data-RoaringBitmap-Shared

 view release on metacpan or  search on metacpan

Shared.xs  view on Meta::CPAN

    RETVAL

SV *
new_memfd(class, name = &PL_sv_undef, container_capacity = 256)
    const char *class
    SV *name
    UV container_capacity
  PREINIT:
    char errbuf[RB_ERR_BUFLEN];
  CODE:
    const char *nm = SvOK(name) ? SvPV_nolen(name) : NULL;   /* undef -> default label */
    RbHandle *h = rb_create_memfd(nm, (uint64_t)container_capacity, errbuf);   /* validates args into errbuf */
    if (!h) croak("Data::RoaringBitmap::Shared->new_memfd: %s", errbuf);
    MAKE_OBJ(class, h);
  OUTPUT:
    RETVAL

SV *
new_from_fd(class, fd)
    const char *class
    int fd
  PREINIT:
    char errbuf[RB_ERR_BUFLEN];
  CODE:
    RbHandle *h = rb_open_fd(fd, errbuf);
    if (!h) croak("Data::RoaringBitmap::Shared->new_from_fd: %s", errbuf);
    MAKE_OBJ(class, h);
  OUTPUT:
    RETVAL

void
DESTROY(self)
    SV *self
  CODE:
    if (sv_isobject(self) && sv_derived_from(self, "Data::RoaringBitmap::Shared")) {
        RbHandle *h = INT2PTR(RbHandle*, SvIV(SvRV(self)));
        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 */
    }

IV
add(self, x)
    SV *self
    UV x
  PREINIT:
    EXTRACT(self);
    int added;
    uint32_t hi;
  CODE:
    /* Range-check BEFORE locking: a croak must never happen under the lock. */
    if (x > RB_UINT32_MAX_UV)
        croak("Data::RoaringBitmap::Shared->add: value %" UVuf " exceeds uint32 (max 4294967295)", x);
    hi = (uint32_t)(x >> 16);
    rb_rwlock_wrlock(h);
    /* Need a free container slot only if this value touches an empty bucket. */
    if (rb_buckets(h)[hi].type == RB_TYPE_NONE && rb_avail_slots(h) == 0) {
        __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);   /* a call that took the write lock counts (matches add_many) */
        rb_rwlock_wrunlock(h);   /* release BEFORE croak */
        croak("Data::RoaringBitmap::Shared->add: container pool exhausted "
              "(grow container_capacity)");
    }
    added = rb_add_locked(h, (uint32_t)x);
    __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
    rb_rwlock_wrunlock(h);
    RETVAL = (IV)added;
  OUTPUT:
    RETVAL

IV
add_many(self, ints)
    SV *self
    SV *ints
  PREINIT:
    EXTRACT(self);
    AV *av;
    SSize_t n, i;
    UV *vals;
    IV total_added;
  CODE:
    if (!SvROK(ints) || SvTYPE(SvRV(ints)) != SVt_PVAV)
        croak("Data::RoaringBitmap::Shared->add_many: expected an array reference");
    av = (AV *)SvRV(ints);
    n = av_len(av) + 1;
    /* Resolve + range-check every value BEFORE the lock (croak-free section). */
    Newx(vals, n > 0 ? n : 1, UV);
    SAVEFREEPV(vals);
    for (i = 0; i < n; i++) {
        SV **e = av_fetch(av, i, 0);
        UV v = (e && SvOK(*e)) ? SvUV(*e) : 0;
        if (v > RB_UINT32_MAX_UV) {
            croak("Data::RoaringBitmap::Shared->add_many: value %" UVuf
                  " exceeds uint32 (max 4294967295)", v);
        }
        vals[i] = v;
    }
    total_added = 0;
    rb_rwlock_wrlock(h);
    for (i = 0; i < n; i++) {
        uint32_t hi = (uint32_t)(vals[i] >> 16);
        if (rb_buckets(h)[hi].type == RB_TYPE_NONE && rb_avail_slots(h) == 0) {
            __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
            rb_rwlock_wrunlock(h);   /* release BEFORE croak; partial adds remain (documented non-atomic) */
            croak("Data::RoaringBitmap::Shared->add_many: container pool exhausted "
                  "after adding %" IVdf " element(s) (grow container_capacity)", total_added);
        }
        total_added += rb_add_locked(h, (uint32_t)vals[i]);
    }
    __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
    rb_rwlock_wrunlock(h);
    RETVAL = total_added;
  OUTPUT:
    RETVAL

bool
contains(self, x)
    SV *self
    UV x
  PREINIT:
    EXTRACT(self);
  CODE:
    if (x > RB_UINT32_MAX_UV) { RETVAL = 0; }   /* a value out of range is simply absent */
    else {
        rb_rwlock_rdlock(h);
        RETVAL = rb_contains_locked(h, (uint32_t)x) ? 1 : 0;
        rb_rwlock_rdunlock(h);
    }
  OUTPUT:
    RETVAL

IV
remove(self, x)
    SV *self
    UV x
  PREINIT:
    EXTRACT(self);
    int removed;
  CODE:
    if (x > RB_UINT32_MAX_UV) { RETVAL = 0; }   /* out of range -> nothing to remove */
    else {
        rb_rwlock_wrlock(h);
        removed = rb_remove_locked(h, (uint32_t)x);
        __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
        rb_rwlock_wrunlock(h);
        RETVAL = (IV)removed;
    }
  OUTPUT:
    RETVAL

UV
cardinality(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    rb_rwlock_rdlock(h);
    RETVAL = (UV)h->hdr->cardinality;
    rb_rwlock_rdunlock(h);
  OUTPUT:
    RETVAL

bool
is_empty(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    rb_rwlock_rdlock(h);
    RETVAL = (h->hdr->cardinality == 0) ? 1 : 0;
    rb_rwlock_rdunlock(h);
  OUTPUT:
    RETVAL

SV *
min(self)
    SV *self
  PREINIT:
    EXTRACT(self);
    uint32_t v;
    int found;
  CODE:
    rb_rwlock_rdlock(h);
    found = rb_min_locked(h, &v);
    rb_rwlock_rdunlock(h);
    RETVAL = found ? newSVuv((UV)v) : &PL_sv_undef;
  OUTPUT:
    RETVAL

SV *
max(self)
    SV *self
  PREINIT:
    EXTRACT(self);
    uint32_t v;
    int found;
  CODE:
    rb_rwlock_rdlock(h);
    found = rb_max_locked(h, &v);
    rb_rwlock_rdunlock(h);
    RETVAL = found ? newSVuv((UV)v) : &PL_sv_undef;
  OUTPUT:
    RETVAL

SV *
to_array(self)
    SV *self
  PREINIT:
    EXTRACT(self);
    AV *av;
    UV card;
  CODE:
    /* Pre-size the AV to the cardinality BEFORE the lock (av_extend can croak
       on OOM; av_store afterward cannot).  The cardinality may grow between
       this read and the fill, but to_array is a best-effort snapshot; we cap
       the fill at the pre-sized length so we never store past the extent. */
    rb_rwlock_rdlock(h);
    card = (UV)h->hdr->cardinality;
    rb_rwlock_rdunlock(h);

    av = newAV();
    if (card) av_extend(av, (SSize_t)card - 1);   /* room for indices 0..card-1 */

    {
        RbBucket *bt;
        UV idx = 0;
        rb_rwlock_rdlock(h);
        bt = rb_buckets(h);
        for (uint32_t hi = 0; hi < RB_NUM_BUCKETS && idx < card; hi++) {
            if (bt[hi].type == RB_TYPE_NONE) continue;
            if (bt[hi].type == RB_TYPE_ARRAY) {
                uint16_t *vals = rb_array(h, bt[hi].container_off);
                for (uint32_t i = 0; i < bt[hi].cardinality && idx < card; i++)
                    av_store(av, (SSize_t)idx++, newSVuv(((UV)hi << 16) | vals[i]));
            } else {
                uint64_t *bits = rb_bitmap(h, bt[hi].container_off);
                for (uint32_t w = 0; w < 1024 && idx < card; w++) {
                    uint64_t word = bits[w];
                    while (word && idx < card) {
                        uint32_t lo = (w << 6) + (uint32_t)__builtin_ctzll(word);
                        av_store(av, (SSize_t)idx++, newSVuv(((UV)hi << 16) | lo));
                        word &= word - 1;   /* clear lowest set bit */
                    }
                }
            }
        }
        rb_rwlock_rdunlock(h);
    }
    RETVAL = newRV_noinc((SV *)av);
  OUTPUT:
    RETVAL

SV *
union(self, other)
    SV *self
    SV *other
  PREINIT:
    EXTRACT(self);
  CODE:
    if (!sv_isobject(other) || !sv_derived_from(other, "Data::RoaringBitmap::Shared"))
        croak("Data::RoaringBitmap::Shared->union: expected a Data::RoaringBitmap::Shared object");
    {
        RbHandle *o = INT2PTR(RbHandle*, SvIV(SvRV(other)));
        if (!o) croak("Attempted to use a destroyed Data::RoaringBitmap::Shared object");
        /* Same underlying bitmap (same handle, or two handles to one mapping
         * sharing a bitmap_id) -> a |= a is a no-op.  Must short-circuit before
         * locking: taking the write lock and the read lock on the SAME rwlock
         * would self-deadlock. */
        if (o == h || o->hdr->bitmap_id == h->hdr->bitmap_id) {
            __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
            SvREFCNT_inc(self);
            RETVAL = self;
        } else {
            uint32_t need;
            rb_lock_pair(h, o);
            need = rb_union_new_slots_needed(h, o);
            if (rb_avail_slots(h) < need) {
                __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
                rb_unlock_pair(h, o);       /* release BEFORE croak */
                croak("Data::RoaringBitmap::Shared->union: container pool exhausted "
                      "(needs %u more container(s); grow container_capacity)", need);
            }
            rb_union_locked(h, o);
            __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
            rb_unlock_pair(h, o);
            SvREFCNT_inc(self);             /* return the receiver for chaining */
            RETVAL = self;
        }
    }
  OUTPUT:
    RETVAL

SV *
intersect(self, other)
    SV *self
    SV *other
  PREINIT:
    EXTRACT(self);
  CODE:
    if (!sv_isobject(other) || !sv_derived_from(other, "Data::RoaringBitmap::Shared"))
        croak("Data::RoaringBitmap::Shared->intersect: expected a Data::RoaringBitmap::Shared object");
    {
        RbHandle *o = INT2PTR(RbHandle*, SvIV(SvRV(other)));
        if (!o) croak("Attempted to use a destroyed Data::RoaringBitmap::Shared object");
        /* Same underlying bitmap (same handle, or two handles to one mapping
         * sharing a bitmap_id) -> a &= a is a no-op.  Short-circuit before
         * locking to avoid self-deadlocking on the one shared rwlock. */
        if (o == h || o->hdr->bitmap_id == h->hdr->bitmap_id) {
            __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
            SvREFCNT_inc(self);
            RETVAL = self;
        } else {
            rb_lock_pair(h, o);             /* intersect never needs new slots */
            rb_intersect_locked(h, o);
            __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
            rb_unlock_pair(h, o);
            SvREFCNT_inc(self);
            RETVAL = self;
        }
    }
  OUTPUT:
    RETVAL

void
clear(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    rb_rwlock_wrlock(h);
    rb_clear_locked(h);
    __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
    rb_rwlock_wrunlock(h);

SV *
stats(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    {
        uint64_t card, ops;
        uint32_t cont_used, cont_cap, bkts_used;
        /* Snapshot under the lock; do all (croak-capable) Perl allocation after
           releasing it -- so an OOM in newHV/newSV* can never strand the lock. */
        rb_rwlock_rdlock(h);
        card      = h->hdr->cardinality;
        cont_used = h->hdr->container_used;
        cont_cap  = h->hdr->container_cap;
        ops       = h->hdr->stat_ops;
        bkts_used = rb_buckets_used(h);
        rb_rwlock_rdunlock(h);

        HV *hv = newHV();
        hv_stores(hv, "cardinality",         newSVuv((UV)card));
        hv_stores(hv, "containers_used",     newSVuv((UV)cont_used));
        hv_stores(hv, "containers_capacity", newSVuv((UV)cont_cap));
        hv_stores(hv, "buckets_used",        newSVuv((UV)bkts_used));
        hv_stores(hv, "ops",                 newSVuv((UV)ops));
        hv_stores(hv, "mmap_size",           newSVuv((UV)h->mmap_size));
        RETVAL = newRV_noinc((SV *)hv);
    }
  OUTPUT:
    RETVAL

SV *
path(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = h->path ? newSVpv(h->path, 0) : &PL_sv_undef;
  OUTPUT:
    RETVAL

int
memfd(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = h->backing_fd;
  OUTPUT:
    RETVAL

void
sync(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:



( run in 0.315 second using v1.01-cache-2.11-cpan-bbe5e583499 )