#include <stdatomic.h>
#include <stdlib.h>
#include <string.h>
#include "rayforce.h"
#include "core/pool.h"
#include "core/profile.h"
#include "mem/heap.h"
#include "ops/internal.h"
#include "ops/hash.h"
#include "ops/cdfuse.h"
#include "table/sym.h"
#define CDF_BYTES_PER_ROW 56
static inline uint64_t cdf_fmix64(uint64_t h) {
h ^= h >> 33;
h *= 0xff51afd7ed558ccdULL;
h ^= h >> 33;
h *= 0xc4ceb9fe1a85ec53ULL;
h ^= h >> 33;
return h;
}
typedef struct {
char* buf;
uint32_t n, cap;
} cdf_buf_t;
#define CDF_REC 32
#define CDF_MAX_PARTS 1024u
typedef struct {
const void* kdata;
int8_t ktype;
uint8_t kattrs;
const void* vdata;
int8_t vtype;
uint8_t vattrs;
uint32_t n_parts, nw;
cdf_buf_t* bufs;
int64_t nrows;
bool dedup;
_Atomic(int) oom;
_Atomic(int64_t) rows_done;
} cdf_p1_ctx_t;
static int64_t cdf_read(const void* data, int64_t r, int8_t type, uint8_t attrs) {
if (type == RAY_F32) {
float v = ((const float*)data)[r];
if (v != v) return INT64_C(0x7fc00000);
if (v == 0) return 0;
uint32_t bits; memcpy(&bits, &v, sizeof(bits)); return bits;
}
if (type == RAY_F64) {
double v = ((const double*)data)[r];
if (v != v) return INT64_C(0x7ff8000000000000);
if (v == 0) return 0;
int64_t bits; memcpy(&bits, &v, sizeof(bits)); return bits;
}
return read_col_i64(data, r, type, attrs);
}
static inline uint64_t cdf_pair_hash(int64_t key, int64_t value) {
return cdf_fmix64(ray_hash_i64(key) * 0x9E3779B97F4A7C15ULL ^ ray_hash_i64(value));
}
typedef struct { int64_t key, value; bool used; } cdf_recent_t;
static bool cdf_repeat(cdf_recent_t* recent, uint64_t hash, int64_t key, int64_t value) {
cdf_recent_t* entry = &recent[hash & 63];
if (entry->used && entry->key == key && entry->value == value) return true;
entry->key = key; entry->value = value; entry->used = true;
return false;
}
static bool cdf_sample_repetition(const cdf_p1_ctx_t* c) {
if (c->nrows < 1024) return false;
cdf_recent_t recent[64] = {{0}};
int repeats = 0;
int64_t step = c->nrows / 1024;
for (int i = 0; i < 1024; i++) {
int64_t row = i * step + ray_hash_i64(i) % step;
int64_t key = cdf_read(c->kdata, row, c->ktype, c->kattrs);
int64_t value = cdf_read(c->vdata, row, c->vtype, c->vattrs);
repeats += cdf_repeat(recent, cdf_pair_hash(key, value), key, value);
}
return repeats >= 128;
}
static void cdf_p1_hist(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; cdf_p1_ctx_t* c = raw;
for (int64_t task = start; task < end; task++) {
cdf_buf_t* my = c->bufs + (size_t)task * c->n_parts;
cdf_recent_t recent[64];
if (c->dedup) memset(recent, 0, sizeof(recent));
int64_t begin = c->nrows / c->nw * task;
int64_t limit = task + 1 == c->nw ? c->nrows : c->nrows / c->nw * (task + 1);
for (int64_t r = begin; r < limit; r++) {
int64_t k = cdf_read(c->kdata, r, c->ktype, c->kattrs);
int64_t v = cdf_read(c->vdata, r, c->vtype, c->vattrs);
uint64_t hash = cdf_pair_hash(k, v);
if (c->dedup && cdf_repeat(recent, hash, k, v)) continue;
uint32_t part = hash & (c->n_parts - 1);
if (my[part].cap == UINT32_MAX) { atomic_store(&c->oom, 1); return; }
my[part].cap++;
}
atomic_fetch_add_explicit(&c->rows_done, limit - begin, memory_order_relaxed);
}
}
static void cdf_p1_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; cdf_p1_ctx_t* c = raw;
for (int64_t task = start; task < end; task++) {
cdf_buf_t* my = c->bufs + (size_t)task * c->n_parts;
cdf_recent_t recent[64];
if (c->dedup) memset(recent, 0, sizeof(recent));
int64_t begin = c->nrows / c->nw * task;
int64_t limit = task + 1 == c->nw ? c->nrows : c->nrows / c->nw * (task + 1);
for (int64_t r = begin; r < limit; r++) {
int64_t k = cdf_read(c->kdata, r, c->ktype, c->kattrs);
int64_t v = cdf_read(c->vdata, r, c->vtype, c->vattrs);
uint64_t hash = cdf_pair_hash(k, v);
if (c->dedup && cdf_repeat(recent, hash, k, v)) continue;
uint32_t part = hash & (c->n_parts - 1);
char* record = my[part].buf + (size_t)my[part].n++ * CDF_REC;
((uint64_t*)record)[0] = hash;
((int64_t*)record)[1] = k;
((int64_t*)record)[2] = v;
((int64_t*)record)[3] = r;
}
atomic_fetch_add_explicit(&c->rows_done, limit - begin, memory_order_relaxed);
}
}
typedef struct {
int64_t key, cnt, first;
} cdf_grp_t;
#define CDF_MERGE_PARTS 64
static inline uint32_t cdf_bucket(uint64_t kh) {
return (uint32_t)((kh >> 32) & (CDF_MERGE_PARTS - 1));
}
typedef struct {
cdf_grp_t* g;
int64_t ng;
int64_t boff[CDF_MERGE_PARTS + 1];
} cdf_part_t;
typedef struct {
cdf_buf_t* bufs;
cdf_part_t* parts;
uint32_t nw, n_parts, part_bits;
_Atomic(int) oom;
_Atomic(int64_t) done;
} cdf_p2_ctx_t;
static void cdf_p2_fn(void* vctx, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
cdf_p2_ctx_t* c = (cdf_p2_ctx_t*)vctx;
if (atomic_load_explicit(&c->oom, memory_order_relaxed)) return;
for (int64_t p = start; p < end; p++) {
int64_t total = 0;
for (uint32_t w = 0; w < c->nw; w++)
total += c->bufs[(size_t)w * c->n_parts + p].n;
c->parts[p].ng = 0;
if (total == 0) {
atomic_fetch_add_explicit(&c->done, 1, memory_order_relaxed);
continue;
}
uint64_t dcap = 8;
while (dcap < (uint64_t)total * 2) dcap <<= 1;
uint64_t dmask = dcap - 1;
ray_t* dh_hdr = NULL;
int64_t* dkv = (int64_t*)scratch_alloc(
&dh_hdr, (size_t)dcap * 2 * sizeof(int64_t) + (size_t)dcap * 4);
if (!dkv) {
atomic_store_explicit(&c->oom, 1, memory_order_relaxed);
return;
}
int32_t* docc = (int32_t*)(dkv + (size_t)dcap * 2);
memset(docc, 0xFF, (size_t)dcap * 4);
ray_t* kh_hdr = NULL;
int64_t* kk = (int64_t*)scratch_alloc(
&kh_hdr, (size_t)dcap * sizeof(int64_t) + (size_t)dcap * 4);
if (!kk) {
scratch_free(dh_hdr);
atomic_store_explicit(&c->oom, 1, memory_order_relaxed);
return;
}
int32_t* kidx = (int32_t*)(kk + dcap);
memset(kidx, 0xFF, (size_t)dcap * 4);
int64_t cap = total < 256 ? total : 256;
cdf_grp_t* grps = (cdf_grp_t*)ray_alloc_raw((size_t)cap * sizeof(cdf_grp_t));
if (!grps) {
scratch_free(dh_hdr);
scratch_free(kh_hdr);
atomic_store_explicit(&c->oom, 1, memory_order_relaxed);
return;
}
int64_t ng = 0;
int failed = 0;
for (uint32_t w = 0; w < c->nw && !failed; w++) {
cdf_buf_t* b = &c->bufs[(size_t)w * c->n_parts + p];
const char* rec = b->buf;
for (uint32_t i = 0; i < b->n; i++, rec += CDF_REC) {
uint64_t ph = ((const uint64_t*)rec)[0];
int64_t k = ((const int64_t*)rec)[1];
int64_t v = ((const int64_t*)rec)[2];
int64_t row = ((const int64_t*)rec)[3];
uint64_t t = cdf_fmix64(ray_hash_i64(k)) & dmask;
int64_t gi = 0;
for (;;) {
if (kidx[t] == -1) {
if (ng >= cap) {
int64_t nc = cap * 2;
if (nc > total && cap < total) nc = total;
cdf_grp_t* ngp = (cdf_grp_t*)ray_realloc_raw(
grps, (size_t)nc * sizeof(cdf_grp_t));
if (!ngp) { failed = 1; break; }
grps = ngp;
cap = nc;
}
if (ng > INT32_MAX) { failed = 1; break; }
kk[t] = k;
kidx[t] = (int32_t)ng;
grps[ng].key = k;
grps[ng].cnt = 0;
grps[ng].first = row;
gi = ng++;
break;
}
if (kk[t] == k) {
gi = kidx[t];
if (row < grps[gi].first) grps[gi].first = row;
break;
}
t = (t + 1) & dmask;
}
if (failed) break;
uint64_t s = (ph >> c->part_bits) & dmask;
for (;;) {
if (docc[s] == -1) {
dkv[s * 2] = k;
dkv[s * 2 + 1] = v;
docc[s] = 1;
grps[gi].cnt++;
break;
}
if (dkv[s * 2] == k && dkv[s * 2 + 1] == v) break;
s = (s + 1) & dmask;
}
}
}
scratch_free(dh_hdr);
scratch_free(kh_hdr);
if (failed) {
ray_free_raw(grps);
atomic_store_explicit(&c->oom, 1, memory_order_relaxed);
return;
}
int64_t* boff = c->parts[p].boff;
int64_t pos[CDF_MERGE_PARTS];
memset(boff, 0, sizeof(int64_t) * (CDF_MERGE_PARTS + 1));
for (int64_t i = 0; i < ng; i++)
boff[cdf_bucket(cdf_fmix64(ray_hash_i64(grps[i].key))) + 1]++;
for (uint32_t m = 0; m < CDF_MERGE_PARTS; m++) {
boff[m + 1] += boff[m];
pos[m] = boff[m];
}
cdf_grp_t* sorted = (cdf_grp_t*)ray_alloc_raw((size_t)ng * sizeof(cdf_grp_t));
if (!sorted) {
ray_free_raw(grps);
atomic_store_explicit(&c->oom, 1, memory_order_relaxed);
return;
}
for (int64_t i = 0; i < ng; i++)
sorted[pos[cdf_bucket(cdf_fmix64(ray_hash_i64(grps[i].key)))]++] = grps[i];
ray_free_raw(grps);
c->parts[p].g = sorted;
c->parts[p].ng = ng;
atomic_fetch_add_explicit(&c->done, 1, memory_order_relaxed);
}
}
typedef struct {
cdf_grp_t* g;
int64_t n;
} cdf_merge_t;
typedef struct {
cdf_part_t* parts;
uint32_t n_parts;
cdf_merge_t* mg;
_Atomic(int) oom;
_Atomic(int64_t) done;
} cdf_p3_ctx_t;
static void cdf_p3_fn(void* vctx, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
cdf_p3_ctx_t* c = (cdf_p3_ctx_t*)vctx;
if (atomic_load_explicit(&c->oom, memory_order_relaxed)) return;
for (int64_t m = start; m < end; m++) {
int64_t total = 0;
for (uint32_t p = 0; p < c->n_parts; p++)
total += c->parts[p].boff[m + 1] - c->parts[p].boff[m];
c->mg[m].n = 0;
if (total == 0) {
atomic_fetch_add_explicit(&c->done, 1, memory_order_relaxed);
continue;
}
uint64_t scap = 8;
while (scap < (uint64_t)total * 2) scap <<= 1;
uint64_t smask = scap - 1;
ray_t* s_hdr = NULL;
int32_t* slot = (int32_t*)scratch_alloc(&s_hdr, (size_t)scap * sizeof(int32_t));
if (!slot) {
atomic_store_explicit(&c->oom, 1, memory_order_relaxed);
return;
}
memset(slot, 0xFF, (size_t)scap * sizeof(int32_t));
int64_t cap = total < 256 ? total : 256, n = 0;
cdf_grp_t* out = (cdf_grp_t*)ray_alloc_raw((size_t)cap * sizeof(cdf_grp_t));
if (!out) {
scratch_free(s_hdr);
atomic_store_explicit(&c->oom, 1, memory_order_relaxed);
return;
}
int failed = 0;
for (uint32_t p = 0; p < c->n_parts && !failed; p++) {
const cdf_grp_t* src = c->parts[p].g;
for (int64_t i = c->parts[p].boff[m]; i < c->parts[p].boff[m + 1]; i++) {
int64_t key = src[i].key;
uint64_t s = cdf_fmix64(ray_hash_i64(key)) & smask;
while (slot[s] != -1 && out[slot[s]].key != key) s = (s + 1) & smask;
if (slot[s] != -1) {
cdf_grp_t* dst = &out[slot[s]];
dst->cnt += src[i].cnt;
if (src[i].first < dst->first) dst->first = src[i].first;
continue;
}
if (n >= cap) {
int64_t nc = cap * 2;
if (nc > total && cap < total) nc = total;
cdf_grp_t* no = (cdf_grp_t*)ray_realloc_raw(
out, (size_t)nc * sizeof(cdf_grp_t));
if (!no) { failed = 1; break; }
out = no;
cap = nc;
}
if (n > INT32_MAX) { failed = 1; break; }
out[n] = src[i];
slot[s] = (int32_t)n;
n++;
}
}
scratch_free(s_hdr);
if (failed) {
ray_free_raw(out);
atomic_store_explicit(&c->oom, 1, memory_order_relaxed);
return;
}
c->mg[m].g = out;
c->mg[m].n = n;
atomic_fetch_add_explicit(&c->done, 1, memory_order_relaxed);
}
}
static int cdf_grp_cmp(const void* a, const void* b) {
int64_t x = ((const cdf_grp_t*)a)->first, y = ((const cdf_grp_t*)b)->first;
return x < y ? -1 : (x > y ? 1 : 0);
}
typedef struct {
const cdf_grp_t* groups;
const uint64_t* order;
uint64_t mask;
int64_t* keys;
int64_t* counts;
int64_t* firsts;
} cdf_emit_t;
static void cdf_emit(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; cdf_emit_t* c = raw;
for (int64_t i = start; i < end; i++) {
const cdf_grp_t* group = &c->groups[c->order ? c->order[i] & c->mask : (uint64_t)i];
c->keys[i] = group->key; c->counts[i] = group->cnt; c->firsts[i] = group->first;
}
}
static uint32_t cdf_part_count(uint32_t nworkers, int64_t nrows) {
uint32_t n = 1;
uint64_t rows = nrows > 0 ? (uint64_t)nrows : 1;
while ((n < nworkers || (uint64_t)n < rows / n + (rows % n != 0)) &&
n < CDF_MAX_PARTS)
n <<= 1;
return n;
}
static int cdf_type_ok(int8_t t) {
return t == RAY_BOOL || t == RAY_U8 || t == RAY_I16 ||
t == RAY_I32 || t == RAY_I64 || t == RAY_DATE ||
t == RAY_TIME || t == RAY_TIMESTAMP || t == RAY_F32 || t == RAY_F64 || RAY_IS_SYM(t);
}
static void cdf_free_all(cdf_buf_t* bufs, char* records, cdf_part_t* parts,
uint32_t n_parts) {
ray_free_raw(records);
ray_free_raw(bufs);
if (parts)
for (uint32_t p = 0; p < n_parts; p++) ray_free_raw(parts[p].g);
ray_free_raw(parts);
}
ray_t* ray_cd_fused(ray_t* key_col, ray_t* val_col, int64_t nrows) {
if (!key_col || !val_col || nrows <= 0) return NULL;
if (!ray_is_vec(key_col) || !ray_is_vec(val_col)) return NULL;
if (!cdf_type_ok(key_col->type) || !cdf_type_ok(val_col->type)) return NULL;
if (key_col->len < nrows || val_col->len < nrows) return NULL;
if (nrows < CDF_MIN_ROWS) return NULL;
ray_pool_t* pool = ray_pool_get();
if (!ray_pool_par_dispatch_ok(pool, nrows, CDF_MIN_ROWS)) return NULL;
int64_t wm = ray_heap_anon_watermark();
if (wm > 0 && (double)nrows * CDF_BYTES_PER_ROW > (double)wm / 4.0)
return NULL;
if ((uint64_t)nrows > SIZE_MAX / CDF_REC) return NULL;
uint32_t workers = ray_pool_total_workers(pool);
uint32_t nw = workers * 4;
if (nw > RAY_POOL_INIT_TASKS) nw = RAY_POOL_INIT_TASKS;
uint32_t n_parts = cdf_part_count(workers, nrows);
char* records = NULL;
size_t nbuf = (size_t)nw * n_parts;
cdf_buf_t* bufs = (cdf_buf_t*)ray_calloc_raw(nbuf * sizeof(cdf_buf_t));
cdf_part_t* parts = (cdf_part_t*)ray_calloc_raw((size_t)n_parts * sizeof(cdf_part_t));
if (!bufs || !parts) {
cdf_free_all(bufs, records, parts, n_parts);
return NULL;
}
cdf_p1_ctx_t p1 = {
.kdata = ray_data(key_col), .ktype = key_col->type, .kattrs = key_col->attrs,
.vdata = ray_data(val_col), .vtype = val_col->type, .vattrs = val_col->attrs,
.n_parts = n_parts, .nw = nw, .bufs = bufs,
.nrows = nrows,
.oom = 0, .rows_done = 0,
};
p1.dedup = cdf_sample_repetition(&p1);
if (p1.dedup) ray_profile_tick("count-distinct: source preaggregation");
ray_profile_tick("count-distinct: prepared");
ray_pool_dispatch_n(pool, cdf_p1_hist, &p1, nw);
if (atomic_load(&p1.oom) || atomic_load(&p1.rows_done) != nrows) {
cdf_free_all(bufs, records, parts, n_parts); return NULL;
}
int64_t record_count = 0;
for (size_t b = 0; b < nbuf; b++) record_count += bufs[b].cap;
records = ray_alloc_raw((size_t)record_count * CDF_REC);
if (!records) { cdf_free_all(bufs, records, parts, n_parts); return NULL; }
int64_t record_offset = 0;
for (size_t b = 0; b < nbuf; b++) {
bufs[b].buf = records + (size_t)record_offset * CDF_REC;
record_offset += bufs[b].cap;
}
atomic_store(&p1.rows_done, 0);
ray_profile_tick("count-distinct: histogram");
ray_pool_dispatch_n(pool, cdf_p1_fn, &p1, nw);
ray_profile_tick("count-distinct: scattered pairs");
if (atomic_load_explicit(&p1.oom, memory_order_relaxed) ||
atomic_load_explicit(&p1.rows_done, memory_order_relaxed) != nrows) {
cdf_free_all(bufs, records, parts, n_parts);
return NULL;
}
cdf_p2_ctx_t p2 = {
.bufs = bufs, .parts = parts, .nw = nw, .n_parts = n_parts,
.part_bits = (uint32_t)__builtin_ctz(n_parts), .oom = 0, .done = 0,
};
ray_pool_dispatch_n(pool, cdf_p2_fn, &p2, n_parts);
ray_profile_tick("count-distinct: deduplicated pairs");
if (atomic_load_explicit(&p2.oom, memory_order_relaxed) ||
atomic_load_explicit(&p2.done, memory_order_relaxed) != (int64_t)n_parts) {
cdf_free_all(bufs, records, parts, n_parts);
return NULL;
}
cdf_merge_t mg[CDF_MERGE_PARTS];
memset(mg, 0, sizeof(mg));
cdf_p3_ctx_t p3 = {
.parts = parts, .n_parts = n_parts, .mg = mg, .oom = 0, .done = 0,
};
ray_pool_dispatch_n(pool, cdf_p3_fn, &p3, CDF_MERGE_PARTS);
ray_profile_tick("count-distinct: merged counts");
if (atomic_load_explicit(&p3.oom, memory_order_relaxed) ||
atomic_load_explicit(&p3.done, memory_order_relaxed) != CDF_MERGE_PARTS) {
for (uint32_t m = 0; m < CDF_MERGE_PARTS; m++) ray_free_raw(mg[m].g);
cdf_free_all(bufs, records, parts, n_parts);
return NULL;
}
cdf_free_all(bufs, records, parts, n_parts);
int64_t ng = 0;
for (uint32_t m = 0; m < CDF_MERGE_PARTS; m++) ng += mg[m].n;
cdf_grp_t* merged = (cdf_grp_t*)ray_alloc_raw((size_t)(ng > 0 ? ng : 1) *
sizeof(cdf_grp_t));
if (!merged) {
for (uint32_t m = 0; m < CDF_MERGE_PARTS; m++) ray_free_raw(mg[m].g);
return NULL;
}
int64_t mo = 0;
for (uint32_t m = 0; m < CDF_MERGE_PARTS; m++) {
if (mg[m].n)
memcpy(merged + mo, mg[m].g, (size_t)mg[m].n * sizeof(cdf_grp_t));
mo += mg[m].n;
ray_free_raw(mg[m].g);
}
uint64_t* order = NULL;
uint64_t mask = 0;
if (ng >= RADIX_SORT_THRESHOLD && nrows > 1) {
unsigned bits = 64 - __builtin_clzll((uint64_t)ng - 1);
unsigned row_bits = 64 - __builtin_clzll((uint64_t)nrows - 1);
if (bits + row_bits <= 64) {
uint64_t* a = ray_alloc_raw((size_t)ng * sizeof(uint64_t));
uint64_t* b = ray_alloc_raw((size_t)ng * sizeof(uint64_t));
if (a && b) {
mask = (UINT64_C(1) << bits) - 1;
for (int64_t i = 0; i < ng; i++) a[i] = ((uint64_t)merged[i].first << bits) | (uint64_t)i;
order = packed_radix_sort_run(pool, a, b, ng, (uint8_t)((bits + row_bits + 7) / 8));
}
if (a != order) ray_free_raw(a);
if (b != order) ray_free_raw(b);
}
}
if (atomic_load_explicit(&pool->cancelled, memory_order_relaxed)) {
ray_free_raw(order); ray_free_raw(merged); return NULL;
}
if (!order) qsort(merged, (size_t)ng, sizeof(cdf_grp_t), cdf_grp_cmp);
ray_profile_tick("count-distinct: ordered groups");
ray_t* keys = ray_vec_new(RAY_I64, ng);
ray_t* cnts = ray_vec_new(RAY_I64, ng);
ray_t* firsts = ray_vec_new(RAY_I64, ng);
ray_t* tbl = ray_table_new(3);
if (!keys || RAY_IS_ERR(keys) || !cnts || RAY_IS_ERR(cnts) ||
!firsts || RAY_IS_ERR(firsts) || !tbl || RAY_IS_ERR(tbl)) {
ray_release(keys); ray_release(cnts); ray_release(firsts); ray_release(tbl);
ray_free_raw(order); ray_free_raw(merged);
return NULL;
}
keys->len = cnts->len = firsts->len = ng;
cdf_emit_t emit = {merged, order, mask, ray_data(keys), ray_data(cnts), ray_data(firsts)};
ray_pool_dispatch(pool, cdf_emit, &emit, ng);
ray_free_raw(order); ray_free_raw(merged);
if (atomic_load_explicit(&pool->cancelled, memory_order_relaxed)) {
ray_release(keys); ray_release(cnts); ray_release(firsts); ray_release(tbl); return NULL;
}
tbl = ray_table_add_col(tbl, ray_sym_intern("k", 1), keys);
tbl = ray_table_add_col(tbl, ray_sym_intern("u", 1), cnts);
tbl = ray_table_add_col(tbl, ray_sym_intern("_first", 6), firsts);
ray_release(keys);
ray_release(cnts);
ray_release(firsts);
if (!tbl || RAY_IS_ERR(tbl)) {
ray_release(tbl);
return NULL;
}
return tbl;
}