#if defined(__APPLE__)
# define _DARWIN_C_SOURCE
#elif !defined(_WIN32)
# define _GNU_SOURCE
#endif
#include "domain.h"
#include "core/profile.h"
#include "core/platform.h"
#include "mem/heap.h"
#include "mem/sys.h"
#include "mem/arena.h"
#include "store/part.h"
#include "store/fileio.h"
#include "ops/hash.h"
#include "core/pool.h"
#include "sym.h"
#include "symimp.h"
#include "store/enccol.h"
#include "store/fsst.h"
#include <stdatomic.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <libgen.h>
#include <sys/stat.h>
#include <limits.h>
#if defined(RAY_OS_WINDOWS)
#include <windows.h>
#elif !defined(RAY_OS_WASM)
#include <pthread.h>
#endif
#include <errno.h>
#if defined(RAY_OS_WINDOWS)
#include <process.h>
#define dom_pid() ((unsigned long long)_getpid())
#else
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <signal.h>
#define dom_pid() ((unsigned long long)getpid())
#endif
#ifndef PATH_MAX
# define PATH_MAX 4096
#endif
#define DOMAIN_STRL_MAGIC 0x4C525453U
#define DOMAIN_STRZ_MAGIC 0x5A525453U
#define DOM_Z_TAB (1 + 255 + 255 * 8)
#define DOM_Z_REC0 ((size_t)12 + DOM_Z_TAB)
typedef enum {
DOM_RUNTIME = 0,
DOM_FILE = 1,
DOM_IMPORT = 2,
} dom_kind_t;
typedef struct dom_retired_s {
void* ptr;
struct dom_retired_s* next;
} dom_retired_t;
typedef struct dom_retired_map_s {
void* map;
size_t size;
struct dom_retired_map_s* next;
} dom_retired_map_t;
#define DOM_IX_STRIDE ((int64_t)RAY_SYM_INDEX_STRIDE)
typedef ray_sym_ixblk_t dom_ixb_t;
#define DOM_IX_MAGIC 0x58525453U
#define DOM_IX_VERSION 5u
#define DOM_IX_HDR 128
#define DOM_IX_HEAD 4096
#define DOM_LEN_LONG 65535
typedef struct dom_blk_s {
int64_t n;
int64_t count;
uint64_t end;
const dom_ixb_t* ib;
dom_ixb_t* own_ib;
} dom_blk_t;
typedef struct dom_raw_snap_s {
const unsigned char* map;
dom_blk_t* blk;
int64_t count;
} dom_raw_snap_t;
typedef struct {
uint64_t* v;
int64_t n, cap;
} dom_ck_t;
#define DOM_SLOT_LOG 11
#define DOM_SLOTS ((int64_t)1 << DOM_SLOT_LOG)
typedef struct {
_Atomic(ray_t*) a[DOM_SLOTS];
} dom_slots_t;
typedef struct {
int64_t n;
_Atomic(dom_slots_t*) c[];
} dom_dir_t;
struct ray_sym_domain_s {
uint8_t kind;
bool immortal;
uint32_t rc;
char* path;
void* map;
size_t map_size;
ray_file_id_t map_id;
int64_t base_count;
int64_t disk_count;
size_t disk_size;
_Atomic(int64_t) count;
_Atomic(dom_dir_t*) atoms;
int64_t atoms_cap;
ray_arena_t* arena;
dom_blk_t* blk;
bool ix_used;
dom_ck_t wck;
_Atomic(int64_t) ix_count;
_Atomic(uint64_t) ix_end;
int64_t ix_covered;
_Atomic(struct dom_raw_snap_s*) raw_snap;
struct dom_retired_map_s* retired_maps;
_Atomic(int64_t*) runtime_lut;
uint64_t* buckets;
uint64_t bucket_mask;
int32_t batch_inflight;
dom_retired_t* retired;
struct ray_symimp_s* imp;
ray_fsst_table_t* z;
ray_fsst_index_t* zix;
size_t rec0;
uint8_t* zbuf;
size_t zcap;
size_t preloaded;
struct ray_sym_domain_s* next;
};
static struct ray_sym_domain_s g_runtime_domain = {
.kind = DOM_RUNTIME,
.immortal = true,
};
ray_sym_domain_t* ray_sym_runtime_domain(void) {
return &g_runtime_domain;
}
static ray_sym_domain_t* g_domains = NULL;
#if defined(RAY_OS_WINDOWS)
static SRWLOCK g_dom_lock = SRWLOCK_INIT;
static inline void dom_lock(void) {
AcquireSRWLockExclusive(&g_dom_lock);
}
static inline void dom_unlock(void) {
ReleaseSRWLockExclusive(&g_dom_lock);
}
#elif defined(RAY_OS_WASM)
static inline void dom_lock(void) {}
static inline void dom_unlock(void) {}
#else
static pthread_mutex_t g_dom_lock = PTHREAD_MUTEX_INITIALIZER;
static inline void dom_lock(void) { pthread_mutex_lock(&g_dom_lock); }
static inline void dom_unlock(void) { pthread_mutex_unlock(&g_dom_lock); }
#endif
static char* dom_realpath(const char* path, char resolved[PATH_MAX]) {
#if defined(RAY_OS_WINDOWS)
if (!_fullpath(resolved, path, PATH_MAX)) return NULL;
if (GetFileAttributesA(resolved) == INVALID_FILE_ATTRIBUTES) return NULL;
return resolved;
#else
return realpath(path, resolved);
#endif
}
static bool dom_path_eq(const char* a, const char* b) {
#if defined(RAY_OS_WINDOWS)
for (;; a++, b++) {
char ca = *a == '\\' ? '/' : *a;
char cb = *b == '\\' ? '/' : *b;
if (ca >= 'A' && ca <= 'Z') ca = (char)(ca - 'A' + 'a');
if (cb >= 'A' && cb <= 'Z') cb = (char)(cb - 'A' + 'a');
if (ca != cb) return false;
if (!ca) return true;
}
#else
return strcmp(a, b) == 0;
#endif
}
static char* dom_resolve_path(const char* path) {
char resolved[PATH_MAX];
if (dom_realpath(path, resolved)) {
size_t n = strlen(resolved);
char* out = (char*)ray_sys_alloc(n + 1);
if (out) memcpy(out, resolved, n + 1);
return out;
}
char tmp[1024];
size_t plen = strlen(path);
if (plen == 0 || plen >= sizeof(tmp)) return NULL;
memcpy(tmp, path, plen + 1);
char* base = basename(tmp);
char bbuf[1024];
size_t blen = strlen(base);
if (blen >= sizeof(bbuf)) return NULL;
memcpy(bbuf, base, blen + 1);
memcpy(tmp, path, plen + 1);
char* dir = dirname(tmp);
char rdir[PATH_MAX];
if (!dom_realpath(dir, rdir)) return NULL;
size_t dlen = strlen(rdir);
char* out = (char*)ray_sys_alloc(dlen + 1 + blen + 1);
if (!out) return NULL;
memcpy(out, rdir, dlen);
out[dlen] = '/';
memcpy(out + dlen + 1, bbuf, blen + 1);
return out;
}
static bool dom_retire(ray_sym_domain_t* d, void* p) {
if (!p) return true;
dom_retired_t* r = (dom_retired_t*)ray_sys_alloc(sizeof(*r));
if (!r) return false;
r->ptr = p;
r->next = d->retired;
d->retired = r;
return true;
}
static bool dom_drop_locked(ray_sym_domain_t* d, void* old) {
if (!old) return true;
if (d->batch_inflight == 0) { ray_sys_free(old); return true; }
return dom_retire(d, old);
}
static bool dom_drop_buckets_locked(ray_sym_domain_t* d, uint64_t* old) {
return dom_drop_locked(d, old);
}
static bool dom_publish_raw_snap(ray_sym_domain_t* d, bool drop_old) {
dom_raw_snap_t* snap = NULL;
if (d->map && d->blk && d->base_count > 0) {
snap = (dom_raw_snap_t*)ray_sys_alloc(sizeof(*snap));
if (!snap) return false;
snap->map = (const unsigned char*)d->map;
snap->blk = d->blk;
snap->count = d->base_count;
}
dom_raw_snap_t* old = atomic_exchange_explicit(&d->raw_snap, snap, memory_order_acq_rel);
if (old) (void)(drop_old ? dom_drop_locked(d, old) : dom_retire(d, old));
return true;
}
static dom_dir_t* dom_dir_new(int64_t slots, dom_dir_t* old) {
int64_t n = (slots + DOM_SLOTS - 1) >> DOM_SLOT_LOG;
if (n < 1) n = 1;
dom_dir_t* dir = (dom_dir_t*)ray_sys_alloc(sizeof(dom_dir_t) +
(size_t)n * sizeof(dom_slots_t*));
if (!dir) return NULL;
dir->n = n;
int64_t keep = old ? (old->n < n ? old->n : n) : 0;
for (int64_t k = 0; k < keep; k++)
atomic_store_explicit(&dir->c[k],
atomic_load_explicit(&old->c[k], memory_order_relaxed),
memory_order_relaxed);
return dir;
}
static inline ray_t* dom_slot_get(dom_dir_t* dir, int64_t pos) {
if (!dir || pos < 0 || (pos >> DOM_SLOT_LOG) >= dir->n) return NULL;
dom_slots_t* c = atomic_load_explicit(&dir->c[pos >> DOM_SLOT_LOG], memory_order_acquire);
return c ? atomic_load_explicit(&c->a[pos & (DOM_SLOTS - 1)], memory_order_acquire)
: NULL;
}
static dom_slots_t* dom_chunk_locked(ray_sym_domain_t* d, int64_t pos) {
dom_dir_t* dir = atomic_load_explicit(&d->atoms, memory_order_relaxed);
_Atomic(dom_slots_t*)* cp = &dir->c[pos >> DOM_SLOT_LOG];
dom_slots_t* c = atomic_load_explicit(cp, memory_order_relaxed);
if (c) return c;
c = (dom_slots_t*)ray_arena_alloc_raw(d->arena, sizeof(dom_slots_t));
if (!c) return NULL;
for (int64_t i = 0; i < DOM_SLOTS; i++)
atomic_store_explicit(&c->a[i], NULL, memory_order_relaxed);
atomic_store_explicit(cp, c, memory_order_release);
return c;
}
static bool dom_slot_set_locked(ray_sym_domain_t* d, int64_t pos, ray_t* a) {
dom_slots_t* c = dom_chunk_locked(d, pos);
if (!c) return false;
atomic_store_explicit(&c->a[pos & (DOM_SLOTS - 1)], a, memory_order_release);
return true;
}
static bool dom_grow_locked(ray_sym_domain_t* d, int64_t need) {
if (need <= d->atoms_cap) return true;
int64_t ncap = d->atoms_cap < DOM_SLOTS ? DOM_SLOTS : d->atoms_cap;
while (ncap < need) ncap *= 2;
dom_dir_t* old = atomic_load_explicit(&d->atoms, memory_order_relaxed);
dom_dir_t* dir = dom_dir_new(ncap, old);
if (!dir) return false;
if (!dom_retire(d, old)) { ray_sys_free(dir); return false; }
atomic_store_explicit(&d->atoms, dir, memory_order_release);
d->atoms_cap = dir->n << DOM_SLOT_LOG;
return true;
}
static bool dom_ck_reserve(dom_ck_t* ck, int64_t want) {
if (want <= ck->cap) return true;
uint64_t* nv = (uint64_t*)ray_sys_alloc((size_t)want * sizeof(uint64_t));
if (!nv) return false;
if (ck->n) memcpy(nv, ck->v, (size_t)ck->n * sizeof(uint64_t));
ray_sys_free(ck->v);
ck->v = nv;
ck->cap = want;
return true;
}
static bool dom_ck_push(dom_ck_t* ck, uint64_t off) {
if (ck->n == ck->cap && !dom_ck_reserve(ck, ck->cap < 64 ? 64 : ck->cap * 2))
return false;
ck->v[ck->n++] = off;
return true;
}
static void dom_ck_free(dom_ck_t* ck) {
ray_sys_free(ck->v);
ck->v = NULL;
ck->n = ck->cap = 0;
}
static inline int64_t dom_ck_count(int64_t entries) {
return (entries + DOM_IX_STRIDE - 1) / DOM_IX_STRIDE;
}
static inline uint32_t dom_rd32(const uint8_t* p) { uint32_t v; memcpy(&v, p, 4); return v; }
static inline uint64_t dom_rd64(const uint8_t* p) { uint64_t v; memcpy(&v, p, 8); return v; }
_Static_assert(sizeof(ray_fsst_table_t) == DOM_Z_TAB, "a symbol table is stored as laid out");
static inline size_t dom_rec0_of(const uint8_t* map) {
return dom_rd32(map) == DOMAIN_STRZ_MAGIC ? DOM_Z_REC0 : 12;
}
static uint8_t* dom_buf_need(uint8_t** buf, size_t* cap, size_t need) {
if (need <= *cap) return *buf;
size_t c = *cap ? *cap : 256;
while (c < need) c *= 2;
uint8_t* nb = (uint8_t*)ray_sys_alloc(c);
if (!nb) return NULL;
ray_sys_free(*buf);
*buf = nb;
*cap = c;
return nb;
}
static inline const uint8_t* dom_z_rec(const uint8_t* map, const dom_ixb_t* ib, int64_t pos,
uint32_t* clen) {
int64_t k = pos / DOM_IX_STRIDE, j = pos % DOM_IX_STRIDE;
const uint8_t* p = map + ib[k].off;
for (int64_t i = 0; i < j; i++) p += 4 + (size_t)dom_rd32(p);
*clen = dom_rd32(p);
return p + 4;
}
static inline size_t dom_z_len(const ray_fsst_table_t* z, const dom_ixb_t* ib, int64_t pos,
const uint8_t* rec, uint32_t clen) {
uint16_t e = ib[pos / DOM_IX_STRIDE].len[pos % DOM_IX_STRIDE];
return e < DOM_LEN_LONG ? e : (size_t)ray_fsst_expanded_len(z, rec, clen);
}
typedef struct {
uint8_t* b[RAY_SYM_RAW_RING];
size_t cap[RAY_SYM_RAW_RING];
unsigned at;
} dom_zring_t;
static _Thread_local dom_zring_t g_zring;
void ray_sym_domain_raw_decode(const ray_sym_domain_raw_t* r, int64_t pos, char* out) {
uint32_t clen;
const uint8_t* rec = dom_z_rec(r->map, r->ib, pos, &clen);
ray_fsst_decode_fast((const ray_fsst_table_t*)r->z, rec, clen, (uint8_t*)out);
}
const char* ray_sym_domain_raw_str_z(const ray_sym_domain_raw_t* r, int64_t pos, size_t* len) {
const ray_fsst_table_t* z = (const ray_fsst_table_t*)r->z;
uint32_t clen;
const uint8_t* rec = dom_z_rec(r->map, r->ib, pos, &clen);
size_t dl = dom_z_len(z, r->ib, pos, rec, clen);
unsigned k = g_zring.at++ % RAY_SYM_RAW_RING;
uint8_t* out = dom_buf_need(&g_zring.b[k], &g_zring.cap[k], dl + 8);
if (!out) { *len = 0; return NULL; }
ray_fsst_decode_fast(z, rec, clen, out);
*len = dl;
return (const char*)out;
}
static dom_blk_t* dom_blk_new(int64_t count, bool own) {
int64_t n = dom_ck_count(count);
size_t head = (sizeof(dom_blk_t) + 7) & ~(size_t)7;
size_t ibb = own ? (size_t)n * sizeof(dom_ixb_t) : 0;
dom_blk_t* b = (dom_blk_t*)ray_sys_alloc(head + ibb);
if (!b) return NULL;
uint8_t* p = (uint8_t*)b + head;
b->n = n;
b->count = count;
b->end = 0;
b->own_ib = own ? (dom_ixb_t*)p : NULL;
b->ib = b->own_ib;
if (own) memset(b->own_ib, 0, ibb);
return b;
}
static inline uint64_t dom_blk_lim(const dom_blk_t* b, int64_t k) {
return k + 1 < b->n ? b->ib[k + 1].off : b->end;
}
#define DOM_RD_CHUNK ((size_t)4 << 20)
typedef struct {
const uint8_t* map;
int fd;
uint8_t* buf;
size_t cap;
uint64_t lo, hi;
uint64_t size;
} dom_src_t;
static inline dom_src_t dom_src_map(const uint8_t* map, uint64_t size) {
dom_src_t s;
memset(&s, 0, sizeof(s));
s.map = map;
s.fd = -1;
s.size = size;
return s;
}
static bool dom_src_open(dom_src_t* s, const char* path, const ray_file_id_t* id, uint64_t size) {
memset(s, 0, sizeof(*s));
s->fd = -1;
s->size = size;
#if !defined(RAY_OS_WINDOWS) && !defined(RAY_OS_WASM)
if (!path || !id || !id->valid) return false;
int fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd < 0) return false;
struct stat st;
if (fstat(fd, &st) != 0 || (uint64_t)st.st_ino != id->ino || (uint64_t)st.st_size != size) {
close(fd);
return false;
}
#if defined(POSIX_FADV_SEQUENTIAL)
(void)posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
#endif
s->fd = fd;
return true;
#else
(void)path; (void)id;
return false;
#endif
}
static void dom_src_close(dom_src_t* s) {
#if !defined(RAY_OS_WINDOWS) && !defined(RAY_OS_WASM)
if (s->fd >= 0) close(s->fd);
#endif
ray_sys_free(s->buf);
s->buf = NULL;
s->fd = -1;
}
static const uint8_t* dom_src_get(dom_src_t* s, uint64_t off, uint64_t len) {
if (off > s->size || len > s->size - off) return NULL;
if (s->map) return s->map + off;
if (off >= s->lo && off + len <= s->hi) return s->buf + (off - s->lo);
#if !defined(RAY_OS_WINDOWS) && !defined(RAY_OS_WASM)
uint64_t n = len > DOM_RD_CHUNK ? len : DOM_RD_CHUNK;
if (n > s->size - off) n = s->size - off;
if (n > s->cap) {
uint8_t* nb = (uint8_t*)ray_sys_alloc((size_t)n);
if (!nb) return NULL;
ray_sys_free(s->buf);
s->buf = nb;
s->cap = (size_t)n;
}
uint64_t got = 0;
while (got < n) {
ssize_t r = pread(s->fd, s->buf + got, (size_t)(n - got), (off_t)(off + got));
if (r < 0 && errno == EINTR) continue;
if (r <= 0) { s->lo = s->hi = 0; return NULL; }
got += (uint64_t)r;
}
#if defined(POSIX_FADV_WILLNEED)
if (off + n < s->size)
(void)posix_fadvise(s->fd, (off_t)(off + n), (off_t)DOM_RD_CHUNK, POSIX_FADV_WILLNEED);
if (s->hi > s->lo)
(void)posix_fadvise(s->fd, (off_t)s->lo, (off_t)(s->hi - s->lo), POSIX_FADV_DONTNEED);
#endif
s->lo = off;
s->hi = off + n;
return s->buf;
#else
return NULL;
#endif
}
static bool dom_blk_walk_src(dom_src_t* src, dom_blk_t* b, int64_t from, size_t* off,
const ray_fsst_table_t* z) {
uint64_t o = *off;
for (int64_t i = from; i < b->count; i++) {
int64_t k = i / DOM_IX_STRIDE, j = i % DOM_IX_STRIDE;
if (!j) b->own_ib[k].off = o;
const uint8_t* h = dom_src_get(src, o, 4);
if (!h) return false;
uint32_t l = dom_rd32(h), sl = l;
if (z) {
const uint8_t* rec = dom_src_get(src, o + 4, l);
if (!rec) return false;
sl = (uint32_t)ray_fsst_expanded_len(z, rec, l);
}
o += 4 + (uint64_t)l;
b->own_ib[k].len[j] = sl < DOM_LEN_LONG ? (uint16_t)sl : (uint16_t)DOM_LEN_LONG;
}
*off = (size_t)o;
return true;
}
static inline const uint8_t* dom_blk_entry(const uint8_t* map, const dom_blk_t* b,
int64_t pos, uint32_t* len) {
int64_t k = pos / DOM_IX_STRIDE, j = pos % DOM_IX_STRIDE;
const uint8_t* p = map + b->ib[k].off;
for (int64_t i = 0; i < j; i++) {
uint16_t l = b->ib[k].len[i];
p += 4 + (l < DOM_LEN_LONG ? (size_t)l : (size_t)dom_rd32(p));
}
*len = dom_rd32(p);
return p + 4;
}
static void dom_blk_save(const char* path, const uint8_t* map, size_t map_size,
const dom_blk_t* b, const ray_file_id_t* id);
static size_t dom_ix_min(void);
static const uint8_t* dom_z_decode_locked(ray_sym_domain_t* d, const uint8_t* rec, uint32_t clen,
size_t dl) {
uint8_t* out = dom_buf_need(&d->zbuf, &d->zcap, dl + 8);
if (!out) return NULL;
ray_fsst_decode_fast(d->z, rec, clen, out);
return out;
}
static const uint8_t* dom_entry_locked(ray_sym_domain_t* d, int64_t pos, uint32_t* len) {
if (pos < 0 || pos >= d->base_count || !d->blk) return NULL;
if (d->z) {
uint32_t clen;
const uint8_t* rec = dom_z_rec((const uint8_t*)d->map, d->blk->ib, pos, &clen);
size_t dl = dom_z_len(d->z, d->blk->ib, pos, rec, clen);
*len = (uint32_t)dl;
return dom_z_decode_locked(d, rec, clen, dl);
}
return dom_blk_entry((const uint8_t*)d->map, d->blk, pos, len);
}
static bool dom_next_locked(ray_sym_domain_t* d, int64_t pos, size_t* off,
const char** p, size_t* len) {
if (pos < d->base_count) {
size_t o = *off;
uint32_t l = dom_rd32((const uint8_t*)d->map + o);
const uint8_t* rec = (const uint8_t*)d->map + o + 4;
*off = o + 4 + l;
if (d->z) {
size_t dl = dom_z_len(d->z, d->blk->ib, pos, rec, l);
const uint8_t* out = dom_z_decode_locked(d, rec, l, dl);
if (!out) return false;
*p = (const char*)out;
*len = dl;
return true;
}
*p = (const char*)rec;
*len = l;
return true;
}
ray_t* a = dom_slot_get(atomic_load_explicit(&d->atoms, memory_order_relaxed), pos);
if (!a) return false;
*p = ray_str_ptr(a);
*len = ray_str_len(a);
return true;
}
static const char* dom_bytes_locked(ray_sym_domain_t* d, int64_t pos, size_t* len) {
ray_t* a = dom_slot_get(atomic_load_explicit(&d->atoms, memory_order_relaxed), pos);
if (a) { *len = ray_str_len(a); return ray_str_ptr(a); }
uint32_t l = 0;
const uint8_t* p = dom_entry_locked(d, pos, &l);
*len = l;
return (const char*)p;
}
static void dom_preload_release(ray_sym_domain_t* d);
static void dom_destroy(ray_sym_domain_t* d) {
dom_preload_release(d);
if (d->imp) ray_symimp_free(d->imp);
ray_sys_free(d->z);
ray_sys_free(d->zix);
ray_sys_free(d->zbuf);
ray_sys_free(atomic_load_explicit(&d->atoms, memory_order_relaxed));
if (d->arena) ray_arena_destroy(d->arena);
ray_sys_free(d->blk);
dom_ck_free(&d->wck);
ray_sys_free(atomic_load_explicit(&d->runtime_lut, memory_order_relaxed));
for (dom_retired_t* r = d->retired; r;) {
dom_retired_t* nxt = r->next;
ray_sys_free(r->ptr);
ray_sys_free(r);
r = nxt;
}
ray_sys_free(d->buckets);
ray_sys_free(atomic_load_explicit(&d->raw_snap, memory_order_relaxed));
for (dom_retired_map_t* rm = d->retired_maps; rm;) {
dom_retired_map_t* nxt = rm->next;
ray_vm_unmap_file(rm->map, rm->size);
ray_sys_free(rm);
rm = nxt;
}
if (d->map) ray_vm_unmap_file(d->map, d->map_size);
ray_sys_free(d->path);
ray_sys_free(d);
}
static size_t dom_ix_min(void) {
const char* e = getenv("RAY_SYM_INDEX_MIN");
if (e && *e) return (size_t)strtoull(e, NULL, 10);
return (size_t)1 << 20;
}
static bool dom_ix_name(char* out, size_t cap, const char* sym, const char* suffix) {
int n = snprintf(out, cap, "%s.ix%s", sym, suffix);
return n > 0 && (size_t)n < cap;
}
static bool dom_fseek_ix(FILE* f, int64_t off) {
#ifdef RAY_OS_WINDOWS
return _fseeki64(f, off, SEEK_SET) == 0;
#else
return fseeko(f, (off_t)off, SEEK_SET) == 0;
#endif
}
static void dom_ix_sweep_tmp(const char* path) {
#if !defined(RAY_OS_WINDOWS) && !defined(RAY_OS_WASM)
char dir[PATH_MAX], pre[PATH_MAX];
const char* slash = strrchr(path, '/');
const char* base = slash ? slash + 1 : path;
size_t dl = slash ? (size_t)(slash - path) : 0;
if (dl >= sizeof(dir)) return;
if (slash) { memcpy(dir, path, dl); dir[dl] = 0; } else { dir[0] = '.'; dir[1] = 0; }
int prl = snprintf(pre, sizeof(pre), "%s.ix.", base);
if (prl <= 0 || (size_t)prl >= sizeof(pre)) return;
DIR* dh = opendir(dir);
if (!dh) return;
struct dirent* e;
while ((e = readdir(dh))) {
size_t el = strlen(e->d_name);
if (el <= (size_t)prl + 4 || strncmp(e->d_name, pre, (size_t)prl) != 0 ||
strcmp(e->d_name + el - 4, ".tmp") != 0) continue;
char* stop = NULL;
long long pid = strtoll(e->d_name + prl, &stop, 10);
if (pid <= 0 || stop != e->d_name + el - 4) continue;
if ((unsigned long long)pid == dom_pid()) continue;
if (kill((pid_t)pid, 0) == 0 || errno != ESRCH) continue;
char full[PATH_MAX];
if (snprintf(full, sizeof(full), "%s/%s", dir, e->d_name) < (int)sizeof(full))
(void)unlink(full);
}
closedir(dh);
#else
(void)path;
#endif
}
static inline uint64_t dom_id_stamp(const ray_file_id_t* id) {
return (uint64_t)id->mtime_ns ^ ((uint64_t)id->ctime_ns * UINT64_C(0x9E3779B97F4A7C15));
}
typedef struct {
int64_t count;
uint64_t end;
uint64_t ino, stamp;
uint64_t last_ckpt;
uint64_t cap;
} dom_ixh_t;
static void dom_ixh_put(uint8_t* h, const dom_ixh_t* x) {
memset(h, 0, DOM_IX_HDR);
uint32_t w = DOM_IX_MAGIC; memcpy(h, &w, 4);
w = DOM_IX_VERSION; memcpy(h + 4, &w, 4);
w = (uint32_t)DOM_IX_STRIDE; memcpy(h + 8, &w, 4);
memcpy(h + 16, &x->count, 8);
memcpy(h + 24, &x->end, 8);
memcpy(h + 32, &x->ino, 8);
memcpy(h + 40, &x->stamp, 8);
memcpy(h + 48, &x->last_ckpt, 8);
memcpy(h + 72, &x->cap, 8);
}
static bool dom_ixh_get(const uint8_t* h, dom_ixh_t* x) {
if (dom_rd32(h) != DOM_IX_MAGIC || dom_rd32(h + 4) != DOM_IX_VERSION ||
dom_rd32(h + 8) != (uint32_t)DOM_IX_STRIDE)
return false;
memcpy(&x->count, h + 16, 8);
x->end = dom_rd64(h + 24);
x->ino = dom_rd64(h + 32);
x->stamp = dom_rd64(h + 40);
x->last_ckpt = dom_rd64(h + 48);
x->cap = dom_rd64(h + 72);
return x->count > 0 && x->end > 12 && x->cap >= (uint64_t)dom_ck_count(x->count) &&
x->cap <= (uint64_t)dom_ck_count(x->count) * 2 + 4096;
}
static void dom_keep_map(ray_sym_domain_t* d, void* m, size_t sz) {
dom_retired_map_t* rm = (dom_retired_map_t*)ray_sys_alloc(sizeof(*rm));
if (!rm) return;
rm->map = m;
rm->size = sz;
rm->next = d->retired_maps;
d->retired_maps = rm;
}
static dom_blk_t* dom_ix_load(const char* path, const uint8_t* map, size_t size, int64_t count,
const ray_file_id_t* id, void** ixmap, size_t* ixsize,
dom_ixh_t* hx) {
char ip[PATH_MAX];
if (count <= 0 || !path || !id || !id->valid || id->size != size ||
!dom_ix_name(ip, sizeof(ip), path, ""))
return NULL;
FILE* f = fopen(ip, "rb");
if (!f) return NULL;
uint8_t h[DOM_IX_HDR];
dom_ixh_t x;
bool ok = fread(h, 1, DOM_IX_HDR, f) == DOM_IX_HDR && dom_ixh_get(h, &x) &&
x.count == count && x.end == size && (x.ino | x.stamp) != 0 &&
x.ino == id->ino && x.stamp == dom_id_stamp(id);
fclose(f);
(void)map;
dom_blk_t* b = ok ? dom_blk_new(count, false) : NULL;
void* m = NULL;
size_t msz = 0;
ray_file_id_t iid;
if (b) {
m = ray_vm_map_file_id(ip, &msz, &iid);
ok = m && msz >= DOM_IX_HDR + x.cap * sizeof(dom_ixb_t) &&
memcmp(m, h, DOM_IX_HDR) == 0;
}
if (!b || !ok) {
if (m) ray_vm_unmap_file(m, msz);
ray_sys_free(b);
return NULL;
}
b->end = size;
b->ib = (const dom_ixb_t*)((const uint8_t*)m + DOM_IX_HDR);
*ixmap = m;
*ixsize = msz;
*hx = x;
return b;
}
static bool dom_write_zeros(FILE* f, uint64_t bytes) {
static const uint8_t z[4096];
while (bytes) {
size_t c = bytes < sizeof(z) ? (size_t)bytes : sizeof(z);
if (fwrite(z, 1, c, f) != c) return false;
bytes -= c;
}
return true;
}
typedef struct {
const dom_ixb_t* pb;
int64_t na;
const uint16_t* lb;
} dom_lsrc_t;
static inline uint16_t dom_ls_get(const dom_lsrc_t* s, int64_t i) {
return i < s->na ? s->pb[i / DOM_IX_STRIDE].len[i % DOM_IX_STRIDE] : s->lb[i - s->na];
}
static bool dom_write_blocks(FILE* f, const uint64_t* ck, const dom_lsrc_t* ls, int64_t count,
int64_t k0, int64_t k1) {
enum { CHUNK = 64 };
dom_ixb_t buf[CHUNK];
for (int64_t k = k0; k < k1;) {
int64_t c = k1 - k < CHUNK ? k1 - k : CHUNK;
memset(buf, 0, sizeof(dom_ixb_t) * (size_t)c);
for (int64_t q = 0; q < c; q++) {
buf[q].off = ck[k + q];
int64_t i0 = (k + q) * DOM_IX_STRIDE;
for (int64_t j = 0; j < DOM_IX_STRIDE && i0 + j < count; j++)
buf[q].len[j] = dom_ls_get(ls, i0 + j);
}
if (fwrite(buf, sizeof(dom_ixb_t), (size_t)c, f) != (size_t)c) return false;
k += c;
}
return true;
}
static void dom_ix_save(const char* path, const uint8_t* map, size_t map_size,
const uint64_t* ck, int64_t n, const dom_lsrc_t* ls,
int64_t count, uint64_t end, const ray_file_id_t* id) {
if (!path || count <= 0 || n != dom_ck_count(count) || end > map_size || map_size < 12 ||
ck[0] != dom_rec0_of(map) ||
ck[n - 1] >= end || (ls->na > 0 && !ls->pb) || (ls->na < count && !ls->lb))
return;
char ip[PATH_MAX], tp[PATH_MAX];
char sfx[32];
snprintf(sfx, sizeof(sfx), ".%llu.tmp", dom_pid());
if (!dom_ix_name(ip, sizeof(ip), path, "") || !dom_ix_name(tp, sizeof(tp), path, sfx)) return;
dom_ixh_t x;
memset(&x, 0, sizeof(x));
x.count = count;
x.end = end;
if (id && id->valid && id->size == end) {
x.ino = id->ino;
x.stamp = dom_id_stamp(id);
}
x.last_ckpt = ck[n - 1];
x.cap = (uint64_t)n + (uint64_t)n / 4 + 64;
uint8_t h[DOM_IX_HDR];
dom_ixh_put(h, &x);
dom_ix_sweep_tmp(path);
FILE* f = fopen(tp, "wb");
bool ok = f && fwrite(h, 1, DOM_IX_HDR, f) == DOM_IX_HDR &&
dom_write_blocks(f, ck, ls, count, 0, n) &&
dom_write_zeros(f, (x.cap - (uint64_t)n) * sizeof(dom_ixb_t));
if (f && fclose(f) != 0) ok = false;
if (ok) ok = ray_file_rename(tp, ip) == RAY_OK;
if (!ok && f) remove(tp);
}
static void dom_ix_refresh(const char* path, const uint64_t* ck, int64_t n,
const dom_lsrc_t* ls, int64_t count, uint64_t end,
int64_t ix_count, uint64_t ix_end, bool stamp) {
if (end < dom_ix_min() || count <= 0 || n != dom_ck_count(count)) return;
size_t size = 0;
ray_file_id_t id;
uint8_t* map = (uint8_t*)ray_vm_map_file_id(path, &size, &id);
if (!map) return;
char ip[PATH_MAX];
FILE* f = ix_count > 0 && end <= size && dom_ix_name(ip, sizeof(ip), path, "")
? fopen(ip, "r+b") : NULL;
uint8_t h[DOM_IX_HDR];
dom_ixh_t x;
bool app = f && fread(h, 1, DOM_IX_HDR, f) == DOM_IX_HDR && dom_ixh_get(h, &x) &&
x.count == ix_count && x.end == ix_end && x.count <= count &&
(uint64_t)n <= x.cap && size >= 12 && ck[0] == dom_rec0_of(map) && ck[n - 1] < end;
if (app) {
int64_t k0 = dom_ck_count(x.count) - 1;
x.count = count;
x.end = end;
x.ino = x.stamp = 0;
if (stamp && id.valid && id.size == end) { x.ino = id.ino; x.stamp = dom_id_stamp(&id); }
x.last_ckpt = ck[n - 1];
dom_ixh_put(h, &x);
app = dom_fseek_ix(f, DOM_IX_HDR + k0 * (int64_t)sizeof(dom_ixb_t)) &&
dom_write_blocks(f, ck, ls, count, k0, n) &&
fflush(f) == 0 && dom_fseek_ix(f, 0) && fwrite(h, 1, DOM_IX_HDR, f) == DOM_IX_HDR;
}
if (f && fclose(f) != 0) app = false;
if (!app) dom_ix_save(path, map, size, ck, n, ls, count, end, stamp ? &id : NULL);
ray_vm_unmap_file(map, size);
}
static void dom_ix_save_file(const char* path, const uint64_t* ck, int64_t n,
const uint16_t* lens, int64_t count, uint64_t end) {
if (end < dom_ix_min()) return;
size_t size = 0;
ray_file_id_t id;
void* map = ray_vm_map_file_id(path, &size, &id);
if (!map) return;
dom_lsrc_t ls = { NULL, 0, lens };
dom_ix_save(path, (const uint8_t*)map, size, ck, n, &ls, count, end, &id);
ray_vm_unmap_file(map, size);
}
static void dom_blk_save(const char* path, const uint8_t* map, size_t map_size,
const dom_blk_t* b, const ray_file_id_t* id) {
uint64_t* ck = (uint64_t*)ray_sys_alloc((size_t)b->n * sizeof(uint64_t));
if (!ck) return;
for (int64_t k = 0; k < b->n; k++) ck[k] = b->ib[k].off;
dom_lsrc_t ls = { b->ib, b->count, NULL };
dom_ix_save(path, map, map_size, ck, b->n, &ls, b->count, b->end, id);
ray_sys_free(ck);
}
void ray_sym_domain_index_save(const char* path, const uint64_t* ckpt, int64_t n,
const uint16_t* lens, int64_t count, uint64_t end) {
if (path && ckpt && lens) dom_ix_save_file(path, ckpt, n, lens, count, end);
}
void ray_sym_domain_index_remove(const char* path) {
char ip[PATH_MAX];
if (path && dom_ix_name(ip, sizeof(ip), path, "")) (void)remove(ip);
}
static bool dom_index_image(ray_sym_domain_t* d, int64_t count) {
const uint8_t* map = (const uint8_t*)d->map;
size_t size = d->map_size;
dom_ixh_t hx;
void* ixm = NULL;
size_t ixs = 0;
dom_blk_t* b = dom_ix_load(d->path, map, size, count, &d->map_id, &ixm, &ixs, &hx);
bool have = b != NULL;
if (have) {
dom_keep_map(d, ixm, ixs);
} else {
b = dom_blk_new(count, true);
if (!b) return false;
b->end = size;
size_t o = d->rec0;
dom_src_t src;
if (!dom_src_open(&src, d->path, &d->map_id, size)) src = dom_src_map(map, size);
bool walked = dom_blk_walk_src(&src, b, 0, &o, d->z) && o == size;
dom_src_close(&src);
if (!walked) { ray_sys_free(b); return false; }
}
d->blk = b;
d->ix_used = have;
d->ix_count = have ? count : 0;
d->ix_end = have ? size : 0;
d->ix_covered = have ? count : 0;
if (!have && count > 0 && size >= dom_ix_min()) {
dom_blk_save(d->path, map, size, b, &d->map_id);
d->ix_count = count;
d->ix_end = size;
} else if (!have && size < dom_ix_min()) {
ray_sym_domain_index_remove(d->path);
}
return true;
}
static ray_t* dom_atom_at_locked(ray_sym_domain_t* d, int64_t pos) {
ray_t* a = dom_slot_get(atomic_load_explicit(&d->atoms, memory_order_relaxed), pos);
if (a) return a;
if (pos < 0 || pos >= d->base_count) return NULL;
uint32_t slen = 0;
const uint8_t* p = dom_entry_locked(d, pos, &slen);
if (!p) return NULL;
a = ray_arena_str(d->arena, (const char*)p, slen);
if (!a || !dom_slot_set_locked(d, pos, a)) return NULL;
return a;
}
static bool dom_extend_from_file_locked(ray_sym_domain_t* d, size_t st_size) {
if (d->z) return false;
int64_t count = atomic_load_explicit(&d->count, memory_order_relaxed);
if (st_size < d->disk_size) return false;
if (count != d->disk_count) return false;
size_t map_size = 0;
ray_file_id_t nid;
void* map = ray_vm_map_file_id(d->path, &map_size, &nid);
if (!map) return false;
const uint8_t* nbase = (const uint8_t*)map;
int64_t fresh_count = -1;
bool ok = map_size >= 12 && dom_rd32(nbase) == DOMAIN_STRL_MAGIC;
if (ok) {
memcpy(&fresh_count, nbase + 4, 8);
ok = fresh_count >= count && fresh_count <= (int64_t)UINT32_MAX;
}
if (ok && count == 0 && fresh_count > 0)
ok = map_size >= 16 && dom_rd32(nbase + 12) == 0;
dom_blk_t* ob = d->blk;
const uint8_t* obase = (const uint8_t*)d->map;
dom_dir_t* cur = atomic_load_explicit(&d->atoms, memory_order_relaxed);
dom_blk_t* nb = NULL;
bool from_ix = false;
void* nixm = NULL;
size_t nixs = 0;
dom_ixh_t nhx;
memset(&nhx, 0, sizeof(nhx));
for (int pass = 0; ok && pass < 2; pass++) {
nb = pass == 0 ? dom_ix_load(d->path, nbase, map_size, fresh_count, &nid, &nixm, &nixs, &nhx)
: NULL;
if (pass == 0 && !nb) pass = 1;
if (!nb) {
nb = dom_blk_new(fresh_count, true);
if (!nb) { ok = false; break; }
nb->end = map_size;
size_t o = 12;
dom_src_t src;
if (!dom_src_open(&src, d->path, &nid, map_size)) src = dom_src_map(nbase, map_size);
bool walked = dom_blk_walk_src(&src, nb, 0, &o, NULL) && o == map_size;
dom_src_close(&src);
if (!walked) { ray_sys_free(nb); nb = NULL; ok = false; break; }
}
from_ix = pass == 0;
bool good = true;
if (d->base_count > 0) {
int64_t full = d->base_count / DOM_IX_STRIDE;
good = map_size >= d->map_size;
for (int64_t k = 0; good && k < full; k++)
good = nb->ib[k].off == ob->ib[k].off && dom_blk_lim(nb, k) == dom_blk_lim(ob, k) &&
memcmp(nb->ib[k].len, ob->ib[k].len, sizeof(nb->ib[k].len)) == 0;
for (int64_t i = full * DOM_IX_STRIDE; good && i < d->base_count; i++)
good = nb->ib[full].len[i - full * DOM_IX_STRIDE] == ob->ib[full].len[i - full * DOM_IX_STRIDE];
if (good && full < ob->n) {
uint64_t at = ob->ib[full].off;
good = at <= d->map_size && at <= map_size && nb->ib[full].off == at &&
memcmp(nbase + at, obase + at, d->map_size - (size_t)at) == 0;
}
if (good) {
size_t hb = d->map_size - 12 < DOM_IX_HEAD ? d->map_size - 12 : DOM_IX_HEAD;
good = memcmp(nbase + 12, obase + 12, hb) == 0;
}
}
size_t ro = d->base_count > 0 ? d->map_size : 12;
for (int64_t i = d->base_count; good && i < count; i++) {
ray_t* a = dom_slot_get(cur, i);
good = a && (i % DOM_IX_STRIDE || nb->ib[i / DOM_IX_STRIDE].off == ro) &&
map_size - ro >= 4;
uint32_t nl = good ? dom_rd32(nbase + ro) : 0;
good = good && (size_t)nl <= map_size - ro - 4 && nl == ray_str_len(a) &&
(nl == 0 || memcmp(nbase + ro + 4, ray_str_ptr(a), nl) == 0);
ro += 4 + (size_t)nl;
}
if (good) break;
ray_sys_free(nb);
nb = NULL;
if (nixm) { ray_vm_unmap_file(nixm, nixs); nixm = NULL; }
if (pass == 1) ok = false;
}
if (ok && fresh_count > count) {
dom_dir_t* narr = dom_dir_new(fresh_count, cur);
if (!narr) {
ok = false;
} else {
dom_blk_t* old_blk = d->blk;
d->blk = nb;
d->map_id = nid;
nb = NULL;
d->ix_used = from_ix;
d->ix_count = from_ix ? nhx.count : 0;
d->ix_end = from_ix ? nhx.end : 0;
d->ix_covered = from_ix ? fresh_count : 0;
if (nixm) { dom_keep_map(d, nixm, nixs); nixm = NULL; }
void* old_map = d->map;
size_t old_map_size = d->map_size;
d->map = map;
d->map_size = map_size;
d->base_count = fresh_count;
dom_ck_free(&d->wck);
atomic_store_explicit(&d->atoms, narr, memory_order_release);
d->atoms_cap = narr->n << DOM_SLOT_LOG;
atomic_store_explicit(&d->count, fresh_count, memory_order_release);
d->disk_count = fresh_count;
d->disk_size = st_size;
bool snap_ok = dom_publish_raw_snap(d, true);
(void)dom_drop_locked(d, cur);
if (snap_ok) (void)dom_drop_locked(d, old_blk);
else (void)dom_retire(d, old_blk);
if (old_map) {
if (snap_ok && d->batch_inflight == 0) {
ray_vm_unmap_file(old_map, old_map_size);
} else {
dom_retired_map_t* rm = (dom_retired_map_t*)ray_sys_alloc(sizeof(*rm));
if (rm) { rm->map = old_map; rm->size = old_map_size; rm->next = d->retired_maps; d->retired_maps = rm; }
}
}
if (d->buckets) {
if (!dom_drop_buckets_locked(d, d->buckets)) {
fprintf(stderr, "rayforce: sym domain '%s': OOM retiring "
"reverse index after external extend\n",
d->path ? d->path : "?");
abort();
}
d->buckets = NULL; d->bucket_mask = 0;
}
int64_t* lut = atomic_load_explicit(&d->runtime_lut, memory_order_relaxed);
if (lut) {
atomic_store_explicit(&d->runtime_lut, NULL, memory_order_release);
if (!dom_drop_locked(d, lut)) {
fprintf(stderr, "rayforce: sym domain '%s': OOM retiring "
"runtime LUT after external extend — cannot "
"keep id translation consistent\n",
d->path ? d->path : "?");
abort();
}
}
return true;
}
}
if (ok && fresh_count == count) d->disk_size = st_size;
ray_sys_free(nb);
if (nixm) ray_vm_unmap_file(nixm, nixs);
ray_vm_unmap_file(map, map_size);
return ok;
}
#define DOM_PL_CHUNK ((size_t)8 << 20)
#define DOM_PL_MIN ((size_t)1 << 20)
static _Atomic(int64_t) g_dom_preloaded;
typedef struct {
int fd;
size_t size;
} dom_pl_ctx_t;
static void dom_preload_fn(void* raw, uint32_t wid, int64_t c0, int64_t c1) {
(void)wid;
const dom_pl_ctx_t* x = (const dom_pl_ctx_t*)raw;
uint8_t* buf = (uint8_t*)ray_sys_alloc(DOM_PL_CHUNK);
if (!buf) return;
for (int64_t c = c0; c < c1; c++) {
size_t off = (size_t)c * DOM_PL_CHUNK;
size_t n = x->size - off < DOM_PL_CHUNK ? x->size - off : DOM_PL_CHUNK;
for (size_t got = 0; got < n;) {
ssize_t r = pread(x->fd, buf + got, n - got, (off_t)(off + got));
if (r < 0 && errno == EINTR) continue;
if (r <= 0) break;
got += (size_t)r;
}
}
ray_sys_free(buf);
}
void ray_sym_domain_preload(ray_sym_domain_t* d, int64_t headroom) {
#if !defined(RAY_OS_WINDOWS) && !defined(RAY_OS_WASM)
if (!d || d->kind != DOM_FILE || d->preloaded) return;
size_t size = d->map_size;
if (!d->map || size < DOM_PL_MIN) return;
const char* e = getenv("RAY_SYM_PRELOAD");
int mode = (e && *e) ? atoi(e) : -1;
if (mode == 0) return;
bool trace = getenv("RAY_SYM_TRACE") != NULL;
int64_t lim = ray_sys_ram_limit();
int64_t anon = ray_heap_anon_committed();
int64_t had = atomic_fetch_add_explicit(&g_dom_preloaded, (int64_t)size, memory_order_relaxed);
int64_t left = lim - anon - had - (int64_t)size;
if (mode < 0 && (lim <= 0 || left < headroom)) {
atomic_fetch_sub_explicit(&g_dom_preloaded, (int64_t)size, memory_order_relaxed);
if (trace)
fprintf(stderr, "sym preload %s: lazy (%zu B; would leave %lld B, needs %lld B)\n",
d->path, size, (long long)left, (long long)headroom);
return;
}
int fd = open(d->path, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
atomic_fetch_sub_explicit(&g_dom_preloaded, (int64_t)size, memory_order_relaxed);
return;
}
d->preloaded = size;
int64_t t0 = trace ? ray_profile_now_ns() : 0;
dom_pl_ctx_t x = { fd, size };
int64_t nc = (int64_t)((size + DOM_PL_CHUNK - 1) / DOM_PL_CHUNK);
ray_pool_t* pool = ray_pool_get();
if (ray_pool_par_dispatch_ok(pool, nc, 2))
ray_pool_dispatch_n(pool, dom_preload_fn, &x, (uint32_t)nc);
else
dom_preload_fn(&x, 0, 0, nc);
close(fd);
if (trace)
fprintf(stderr, "sym preload %s: read %zu B in %.2f s (leaves %lld B, needs %lld B)\n",
d->path, size, (double)(ray_profile_now_ns() - t0) / 1e9,
(long long)left, (long long)headroom);
#else
(void)d; (void)headroom;
#endif
}
static void dom_preload_release(ray_sym_domain_t* d) {
if (d->preloaded)
atomic_fetch_sub_explicit(&g_dom_preloaded, (int64_t)d->preloaded, memory_order_relaxed);
d->preloaded = 0;
}
static ray_sym_domain_t* dom_open_impl(const char* path, bool create) {
if (!path) return NULL;
ray_parted_cache_prune();
char* rpath = dom_resolve_path(path);
if (!rpath) return NULL;
struct stat st;
bool exists = (stat(rpath, &st) == 0);
if (!exists && !create) { ray_sys_free(rpath); return NULL; }
dom_lock();
for (ray_sym_domain_t* d = g_domains; d; d = d->next) {
if (dom_path_eq(d->path, rpath)) {
size_t cur_size = exists ? (size_t)st.st_size : 0;
if (cur_size != d->disk_size &&
!dom_extend_from_file_locked(d, cur_size)) {
dom_unlock();
ray_sys_free(rpath);
return NULL;
}
d->rc++;
dom_unlock();
ray_sys_free(rpath);
return d;
}
}
dom_unlock();
ray_sym_domain_t* d = (ray_sym_domain_t*)ray_sys_alloc((size_t)(1) * (sizeof(*d)));
if (!d) { ray_sys_free(rpath); return NULL; }
d->kind = DOM_FILE;
d->rc = 1;
atomic_store_explicit(&d->raw_snap, NULL, memory_order_relaxed);
d->retired_maps = NULL;
d->path = rpath;
d->rec0 = 12;
d->arena = ray_arena_new(64 * 1024);
if (!d->arena) { ray_sys_free(d); ray_sys_free(rpath); return NULL; }
if (exists) {
d->map = ray_vm_map_file_id(rpath, &d->map_size, &d->map_id);
int64_t count = -1;
const uint8_t* m = (const uint8_t*)d->map;
if (m && d->map_size >= 12 && dom_rd32(m) == DOMAIN_STRL_MAGIC)
memcpy(&count, m + 4, 8);
if (m && d->map_size >= DOM_Z_REC0 && dom_rd32(m) == DOMAIN_STRZ_MAGIC) {
d->z = (ray_fsst_table_t*)ray_sys_alloc(sizeof(ray_fsst_table_t));
d->zix = (ray_fsst_index_t*)ray_sys_alloc(sizeof(ray_fsst_index_t));
if (d->z && d->zix) {
memcpy(d->z, m + 12, DOM_Z_TAB);
ray_fsst_index_build(d->z, d->zix);
d->rec0 = DOM_Z_REC0;
memcpy(&count, m + 4, 8);
}
}
if (count < 0 || count > (int64_t)UINT32_MAX || !dom_index_image(d, count)) {
dom_destroy(d);
return NULL;
}
dom_dir_t* dir = count > 0 ? dom_dir_new(count, NULL) : NULL;
if (count > 0 && !dir) { dom_destroy(d); return NULL; }
atomic_store_explicit(&d->atoms, dir, memory_order_relaxed);
atomic_store_explicit(&d->count, count, memory_order_relaxed);
d->atoms_cap = dir ? dir->n << DOM_SLOT_LOG : 0;
d->base_count = count;
(void)dom_publish_raw_snap(d, false);
d->disk_count = count;
d->disk_size = d->map_size;
}
dom_lock();
for (ray_sym_domain_t* e = g_domains; e; e = e->next) {
if (dom_path_eq(e->path, d->path)) {
e->rc++;
dom_unlock();
dom_destroy(d);
return e;
}
}
d->next = g_domains;
g_domains = d;
dom_unlock();
return d;
}
ray_sym_domain_t* ray_sym_domain_open(const char* path) {
return dom_open_impl(path, false);
}
ray_sym_domain_t* ray_sym_domain_open_or_create(const char* path) {
return dom_open_impl(path, true);
}
ray_sym_domain_t* ray_sym_domain_create_import(const char* path) {
if (!path) return NULL;
ray_sym_domain_t* d = (ray_sym_domain_t*)ray_sys_alloc(sizeof(*d));
if (!d) return NULL;
memset(d, 0, sizeof(*d));
d->kind = DOM_IMPORT;
d->rc = 1;
size_t pl = strlen(path);
d->path = (char*)ray_sys_alloc(pl + 1);
if (d->path) memcpy(d->path, path, pl + 1);
if (d->path) d->imp = ray_symimp_create(path, &d->count);
if (!d->imp) { ray_sys_free(d->path); ray_sys_free(d); return NULL; }
return d;
}
bool ray_sym_domain_import_stats(ray_sym_domain_t* dom, struct ray_symimp_stats_s* out) {
if (!dom || dom->kind != DOM_IMPORT) return false;
ray_symimp_stats(dom->imp, out);
return true;
}
struct ray_symimp_s* ray_sym_domain_import(ray_sym_domain_t* dom) {
return dom && dom->kind == DOM_IMPORT ? dom->imp : NULL;
}
ray_sym_domain_t* ray_sym_domain_new(void) {
ray_sym_domain_t* d = ray_sys_alloc(sizeof(*d));
if (!d) return NULL;
d->kind = DOM_FILE;
d->rc = 1;
d->rec0 = 12;
atomic_store_explicit(&d->raw_snap, NULL, memory_order_relaxed);
d->arena = ray_arena_new(64 * 1024);
if (!d->arena) { ray_sys_free(d); return NULL; }
return d;
}
void ray_sym_domain_retain(ray_sym_domain_t* dom) {
if (!dom || dom->immortal) return;
dom_lock();
dom->rc++;
dom_unlock();
}
void ray_sym_domain_release(ray_sym_domain_t* dom) {
if (!dom || dom->immortal) return;
dom_lock();
if (--dom->rc > 0) {
dom_unlock();
return;
}
ray_sym_domain_t** pp = &g_domains;
while (*pp && *pp != dom) pp = &(*pp)->next;
if (*pp) *pp = dom->next;
dom_unlock();
dom_destroy(dom);
}
ray_t* ray_sym_domain_str(ray_sym_domain_t* dom, int64_t pos) {
if (!dom) return NULL;
if (dom->kind == DOM_RUNTIME) return ray_sym_str(pos);
if (dom->kind == DOM_IMPORT) return NULL;
int64_t count = atomic_load_explicit(&dom->count, memory_order_acquire);
if (pos < 0 || pos >= count) return NULL;
ray_t* a = dom_slot_get(atomic_load_explicit(&dom->atoms, memory_order_acquire), pos);
if (a) return a;
dom_lock();
a = dom_atom_at_locked(dom, pos);
dom_unlock();
return a;
}
bool ray_sym_domain_raw_pin(ray_sym_domain_t* dom, ray_sym_domain_raw_t* out) {
if (!dom || !out || dom->kind != DOM_FILE) return false;
dom_raw_snap_t* snap = atomic_load_explicit(&dom->raw_snap, memory_order_acquire);
if (!snap) return false;
out->map = snap->map;
out->ib = snap->blk->ib;
out->count = snap->count;
out->blk = snap->blk;
out->dom = dom;
out->z = dom->z;
return true;
}
int64_t ray_sym_domain_strlen(ray_sym_domain_t* dom, int64_t pos) {
if (!dom || pos < 0) return -1;
ray_sym_domain_raw_t r;
if (dom->kind == DOM_FILE && ray_sym_domain_raw_pin(dom, &r) && pos < r.count)
return ray_sym_domain_raw_len(&r, pos);
ray_t* s = ray_sym_domain_str(dom, pos);
return s ? (int64_t)ray_str_len(s) : -1;
}
bool ray_sym_domain_ix_info(ray_sym_domain_t* dom, ray_sym_domain_ix_info_t* out) {
if (!dom || !out || dom->kind != DOM_FILE) return false;
dom_lock();
out->from_index = dom->ix_used;
out->lens_mapped = dom->blk && dom->blk->ib && !dom->blk->own_ib;
out->covered = dom->ix_covered;
dom_unlock();
return true;
}
void ray_sym_domain_raw_unpin(ray_sym_domain_t* dom) {
(void)dom;
}
static const int64_t g_empty_lut[1] = { -1 };
const int64_t* ray_sym_domain_runtime_lut(ray_sym_domain_t* dom) {
if (!dom || dom->kind == DOM_RUNTIME) return NULL;
if (dom->kind == DOM_IMPORT) return NULL;
int64_t* lut = atomic_load_explicit(&dom->runtime_lut, memory_order_acquire);
if (lut) return lut;
if (atomic_load_explicit(&dom->count, memory_order_acquire) == 0)
return g_empty_lut;
dom_lock();
lut = atomic_load_explicit(&dom->runtime_lut, memory_order_relaxed);
if (!lut) {
int64_t count = atomic_load_explicit(&dom->count, memory_order_relaxed);
lut = (int64_t*)ray_sys_alloc((size_t)count * sizeof(int64_t));
if (!lut) { dom_unlock(); return NULL; }
size_t off = dom->rec0;
for (int64_t i = 0; i < count; i++) {
const char* p = NULL;
size_t l = 0;
if (!dom_next_locked(dom, i, &off, &p, &l)) {
ray_sys_free(lut); dom_unlock(); return NULL;
}
lut[i] = ray_sym_intern(p, l);
if (lut[i] < 0) {
ray_sys_free(lut);
dom_unlock();
return NULL;
}
}
atomic_store_explicit(&dom->runtime_lut, lut, memory_order_release);
}
dom_unlock();
return lut;
}
static bool dom_build_index_locked(ray_sym_domain_t* d, int64_t extra) {
int64_t count = atomic_load_explicit(&d->count, memory_order_relaxed);
uint64_t need = (uint64_t)count + (uint64_t)extra;
uint64_t cap = 16;
while ((double)need > 0.7 * (double)cap) {
if (cap > (UINT64_MAX >> 1)) return false;
cap <<= 1;
}
uint64_t* buckets = (uint64_t*)ray_sys_alloc((size_t)((size_t)cap) * (sizeof(uint64_t)));
if (!buckets) return false;
uint64_t mask = cap - 1;
if (d->buckets) {
uint64_t old_cap = d->bucket_mask + 1;
for (uint64_t i = 0; i < old_cap; i++) {
uint64_t e = d->buckets[i];
if (e == 0) continue;
uint64_t slot = (uint32_t)(e >> 32) & mask;
while (buckets[slot] != 0) slot = (slot + 1) & mask;
buckets[slot] = e;
}
} else {
size_t off = d->rec0;
for (int64_t i = 0; i < count; i++) {
const char* p = NULL;
size_t l = 0;
if (!dom_next_locked(d, i, &off, &p, &l)) { ray_sys_free(buckets); return false; }
uint32_t h = (uint32_t)ray_hash_bytes(p, l);
uint64_t slot = h & mask;
while (buckets[slot] != 0) slot = (slot + 1) & mask;
buckets[slot] = ((uint64_t)h << 32) | ((uint64_t)(uint32_t)i + 1);
}
}
if (d->buckets && !dom_drop_buckets_locked(d, d->buckets)) {
ray_sys_free(buckets);
return false;
}
d->buckets = buckets;
d->bucket_mask = mask;
return true;
}
static int64_t dom_probe_locked(ray_sym_domain_t* d, uint32_t h,
const char* str, size_t len) {
uint64_t mask = d->bucket_mask;
uint64_t slot = h & mask;
while (d->buckets[slot] != 0) {
uint64_t e = d->buckets[slot];
if ((uint32_t)(e >> 32) == h) {
int64_t pos = (int64_t)(uint32_t)e - 1;
size_t l = 0;
const char* p = dom_bytes_locked(d, pos, &l);
if (p && l == len && (len == 0 || memcmp(p, str, len) == 0))
return pos;
}
slot = (slot + 1) & mask;
}
return -1;
}
int64_t ray_sym_domain_find(ray_sym_domain_t* dom, const char* str, size_t len) {
if (!dom || !str) return -1;
if (dom->kind == DOM_RUNTIME) return ray_sym_find(str, len);
if (dom->kind == DOM_IMPORT) return -1;
if (atomic_load_explicit(&dom->count, memory_order_acquire) == 0)
return -1;
dom_lock();
if (len == 0) {
ray_t* a0 = dom_atom_at_locked(dom, 0);
if (a0 && ray_str_len(a0) == 0) { dom_unlock(); return 0; }
}
if (!dom->buckets && !dom_build_index_locked(dom, 0)) {
dom_unlock();
return -1;
}
uint32_t h = (uint32_t)ray_hash_bytes(str, len);
int64_t found = dom_probe_locked(dom, h, str, len);
dom_unlock();
return found;
}
static int64_t dom_append_locked(ray_sym_domain_t* d, uint32_t h,
const char* str, size_t len) {
int64_t count = atomic_load_explicit(&d->count, memory_order_relaxed);
if (count >= (int64_t)UINT32_MAX) return -1;
if (!dom_grow_locked(d, count + 1)) return -1;
if (!dom_chunk_locked(d, count)) return -1;
uint64_t cap = d->bucket_mask + 1;
if ((double)(count + 1) > 0.7 * (double)cap &&
!dom_build_index_locked(d, 1))
return -1;
ray_t* s = ray_arena_str(d->arena, str, len);
if (!s) return -1;
(void)dom_slot_set_locked(d, count, s);
atomic_store_explicit(&d->count, count + 1, memory_order_release);
uint64_t mask = d->bucket_mask;
uint64_t slot = h & mask;
while (d->buckets[slot] != 0) slot = (slot + 1) & mask;
d->buckets[slot] = ((uint64_t)h << 32) | ((uint64_t)(uint32_t)count + 1);
int64_t* lut = atomic_load_explicit(&d->runtime_lut, memory_order_relaxed);
if (lut) {
if (!dom_retire(d, lut)) {
fprintf(stderr, "rayforce: sym domain '%s': OOM retiring runtime "
"LUT after append — cannot keep id translation "
"consistent\n", d->path ? d->path : "?");
abort();
}
atomic_store_explicit(&d->runtime_lut, NULL, memory_order_release);
}
return count;
}
int64_t ray_sym_domain_intern(ray_sym_domain_t* dom, const char* str, size_t len) {
if (!dom || !str) return -1;
if (dom->kind == DOM_RUNTIME) return ray_sym_intern(str, len);
if (dom->kind == DOM_IMPORT) {
uint64_t h = ray_hash_bytes(str, len);
int64_t pos = -1;
return ray_symimp_intern_batch(dom->imp, 1, &str, &len, &h, &pos) ? pos : -1;
}
dom_lock();
if (!dom->buckets && !dom_build_index_locked(dom, 1)) {
dom_unlock();
return -1;
}
if (atomic_load_explicit(&dom->count, memory_order_relaxed) == 0 &&
len != 0) {
uint32_t h0 = (uint32_t)ray_hash_bytes("", 0);
if (dom_append_locked(dom, h0, "", 0) != 0) {
dom_unlock();
return -1;
}
}
uint32_t h = (uint32_t)ray_hash_bytes(str, len);
int64_t pos = dom_probe_locked(dom, h, str, len);
if (pos < 0) pos = dom_append_locked(dom, h, str, len);
dom_unlock();
return pos;
}
typedef struct {
const uint64_t* buckets;
uint64_t mask;
dom_dir_t* atoms;
int64_t count;
ray_sym_domain_raw_t raw;
const char* const* strs;
const size_t* lens;
const uint32_t* hashes;
int64_t* out_pos;
int64_t* miss;
int64_t* uniq;
int64_t* part_off;
int64_t* uniq_n;
int64_t* tab;
int64_t* tab_off;
int64_t* ui;
int64_t* uoff;
char* region;
int64_t base;
dom_dir_t* atoms_w;
_Atomic(uint8_t)* done;
int64_t insert_total;
_Atomic(bool) unsure;
int part_shift;
} dom_batch_ctx_t;
static void dom_batch_probe_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
const dom_batch_ctx_t* b = (const dom_batch_ctx_t*)raw;
for (int64_t i = start; i < end; i++) {
uint32_t h = b->hashes[i];
size_t len = b->lens[i];
const char* s = b->strs[i];
uint64_t slot = h & b->mask;
int64_t found = -1;
for (;;) {
uint64_t e = atomic_load_explicit((_Atomic(uint64_t)*)&b->buckets[slot],
memory_order_relaxed);
if (e == 0) break;
if ((uint32_t)(e >> 32) == h) {
int64_t pos = (int64_t)(uint32_t)e - 1;
if (pos < b->count) {
const char* p = NULL;
size_t l = 0;
ray_t* a = dom_slot_get(b->atoms, pos);
if (a) { p = ray_str_ptr(a); l = ray_str_len(a); }
else if (pos < b->raw.count) p = ray_sym_domain_raw_str(&b->raw, pos, &l);
else atomic_store_explicit((_Atomic(bool)*)&b->unsure, true, memory_order_relaxed);
if (p && l == len && (len == 0 || memcmp(p, s, len) == 0)) {
found = pos;
break;
}
}
}
slot = (slot + 1) & b->mask;
}
b->out_pos[i] = found;
}
}
static void dom_batch_dedupe_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
dom_batch_ctx_t* b = (dom_batch_ctx_t*)raw;
for (int64_t p = start; p < end; p++) {
int64_t lo = b->part_off[p], hi = b->part_off[p + 1];
int64_t cnt = hi - lo;
if (cnt == 0) { b->uniq_n[p] = 0; continue; }
uint64_t cap = (uint64_t)(b->tab_off[p + 1] - b->tab_off[p]);
int64_t* tab = b->tab + b->tab_off[p];
memset(tab, 0xff, (size_t)cap * sizeof(int64_t));
uint64_t mask = cap - 1;
int64_t k = 0;
for (int64_t j = lo; j < hi; j++) {
int64_t i = b->miss[j];
uint32_t h = b->hashes[i];
uint64_t slot = h & mask;
int64_t rep = -1;
while (tab[slot] >= 0) {
int64_t r = tab[slot];
if (b->hashes[r] == h && b->lens[r] == b->lens[i] &&
(b->lens[i] == 0 || memcmp(b->strs[r], b->strs[i], b->lens[i]) == 0)) {
rep = r;
break;
}
slot = (slot + 1) & mask;
}
if (rep >= 0) {
b->out_pos[i] = -(rep + 2);
} else {
tab[slot] = i;
b->uniq[lo + k++] = i;
}
}
b->uniq_n[p] = k;
}
}
static void dom_batch_insert_fn(void* raw, uint32_t wid, int64_t k0, int64_t k1) {
(void)wid;
dom_batch_ctx_t* b = (dom_batch_ctx_t*)raw;
for (int64_t k = k0; k < k1; k++) {
int64_t i = b->ui[k];
int64_t pos = b->base + k;
ray_t* s = ray_arena_str_at(b->region + b->uoff[k], b->strs[i], b->lens[i]);
dom_slots_t* c = atomic_load_explicit(&b->atoms_w->c[pos >> DOM_SLOT_LOG],
memory_order_relaxed);
atomic_store_explicit(&c->a[pos & (DOM_SLOTS - 1)], s, memory_order_relaxed);
uint32_t h = b->hashes[i];
uint64_t e = ((uint64_t)h << 32) | ((uint64_t)(uint32_t)pos + 1);
uint64_t slot = h & b->mask;
for (;;) {
uint64_t cur = 0;
if (atomic_compare_exchange_strong_explicit((_Atomic(uint64_t)*)&b->buckets[slot],
&cur, e, memory_order_relaxed, memory_order_relaxed))
break;
slot = (slot + 1) & b->mask;
}
}
}
#define DOM_INSERT_GRAIN 4096
static void dom_batch_insert_task_fn(void* raw, uint32_t wid, int64_t t0, int64_t t1) {
dom_batch_ctx_t* b = (dom_batch_ctx_t*)raw;
for (int64_t t = t0; t < t1; t++) {
int64_t k0 = t * DOM_INSERT_GRAIN;
int64_t k1 = k0 + DOM_INSERT_GRAIN;
if (k1 > b->insert_total) k1 = b->insert_total;
dom_batch_insert_fn(raw, wid, k0, k1);
atomic_store_explicit(&b->done[t], 1, memory_order_release);
}
}
static void dom_batch_repeat_fn(void* raw, uint32_t wid, int64_t j0, int64_t j1) {
(void)wid;
dom_batch_ctx_t* b = (dom_batch_ctx_t*)raw;
for (int64_t j = j0; j < j1; j++) {
int64_t i = b->miss[j];
int64_t v = b->out_pos[i];
if (v < -1) b->out_pos[i] = b->out_pos[-(v + 2)];
}
}
static inline bool dom_batch_cancelled(ray_pool_t* pool) {
if (ray_interrupted()) return true;
return pool && atomic_load_explicit(&pool->cancelled, memory_order_relaxed);
}
#ifdef DEBUG
static _Atomic(int) g_dom_batch_cancel_phase_for_test = 0;
void ray_sym_domain_batch_cancel_for_test(int phase) {
atomic_store_explicit(&g_dom_batch_cancel_phase_for_test, phase,
memory_order_relaxed);
}
static void dom_batch_cancel_for_test(int phase) {
int expected = phase;
if (atomic_compare_exchange_strong_explicit(
&g_dom_batch_cancel_phase_for_test, &expected, 0,
memory_order_relaxed, memory_order_relaxed))
ray_request_interrupt();
}
#else
#define dom_batch_cancel_for_test(...) ((void)0)
#endif
static bool dom_batch_append_serial_locked(ray_sym_domain_t* dom, int64_t n,
const char* const* strs, const size_t* lens,
const uint32_t* hashes, int64_t* out_pos) {
for (int64_t i = 0; i < n; i++) {
if (out_pos[i] >= 0) continue;
int64_t pos = dom_probe_locked(dom, hashes[i], strs[i], lens[i]);
if (pos < 0) pos = dom_append_locked(dom, hashes[i], strs[i], lens[i]);
if (pos < 0) return false;
out_pos[i] = pos;
}
return true;
}
bool ray_sym_domain_intern_batch(ray_sym_domain_t* dom, int64_t n,
const char* const* strs, const size_t* lens,
const uint32_t* hashes, int64_t* out_pos) {
if (!dom || n < 0) return false;
if (n == 0) return true;
if (ray_interrupted()) return false;
if (dom->kind == DOM_IMPORT) {
uint64_t h64[1024];
for (int64_t o = 0; o < n; o += 1024) {
int64_t k = n - o < 1024 ? n - o : 1024;
for (int64_t i = 0; i < k; i++)
h64[i] = strs[o + i] ? ray_hash_bytes(strs[o + i], lens[o + i]) : ray_hash_bytes("", 0);
if (!ray_symimp_intern_batch(dom->imp, k, strs + o, lens + o, h64, out_pos + o)) return false;
}
return true;
}
if (dom->kind == DOM_RUNTIME) {
for (int64_t i = 0; i < n; i++) {
int64_t id = ray_sym_intern_prehashed(hashes[i], strs[i], lens[i]);
if (id < 0) return false;
out_pos[i] = id;
}
return true;
}
dom_batch_ctx_t b;
memset(&b, 0, sizeof(b));
dom_lock();
int64_t count = atomic_load_explicit(&dom->count, memory_order_relaxed);
if (!dom->buckets ||
(double)(count + n + 1) > 0.7 * (double)(dom->bucket_mask + 1)) {
if (!dom_build_index_locked(dom, n + 1)) { dom_unlock(); return false; }
}
if (count == 0) {
uint32_t h0 = (uint32_t)ray_hash_bytes("", 0);
if (dom_append_locked(dom, h0, "", 0) != 0) { dom_unlock(); return false; }
}
b.buckets = dom->buckets;
b.mask = dom->bucket_mask;
b.atoms = atomic_load_explicit(&dom->atoms, memory_order_acquire);
b.count = atomic_load_explicit(&dom->count, memory_order_acquire);
dom->batch_inflight++;
dom_unlock();
ray_profile_tick("dom batch: index ready");
if (!ray_sym_domain_raw_pin(dom, &b.raw)) b.raw.count = 0;
b.strs = strs; b.lens = lens; b.hashes = hashes; b.out_pos = out_pos;
ray_pool_t* pool = ray_pool_get();
bool par = ray_pool_par_dispatch_ok(pool, n, 4096);
dom_batch_cancel_for_test(RAY_SYM_DOMAIN_CANCEL_PROBE);
if (par) ray_pool_dispatch(pool, dom_batch_probe_fn, &b, n);
else dom_batch_probe_fn(&b, 0, 0, n);
if (dom_batch_cancelled(pool)) {
dom_lock();
dom->batch_inflight--;
dom_unlock();
return false;
}
int n_part = 1;
if (par) {
int64_t want = (int64_t)ray_pool_total_workers(pool) * 4;
if (want > n / 4096) want = n / 4096;
while (n_part < want && n_part < 1024) n_part <<= 1;
}
b.part_shift = 31;
for (int p = n_part; p > 2; p >>= 1) b.part_shift--;
if (n_part == 1) n_part = 2;
int64_t n_miss = 0;
for (int64_t i = 0; i < n; i++) n_miss += (out_pos[i] < 0);
if (n_miss == 0) {
dom_lock();
dom->batch_inflight--;
dom_unlock();
return true;
}
b.miss = (int64_t*)ray_sys_alloc((size_t)n_miss * sizeof(int64_t));
b.uniq = (int64_t*)ray_sys_alloc((size_t)n_miss * sizeof(int64_t));
b.part_off = (int64_t*)ray_sys_alloc((size_t)(n_part + 1) * sizeof(int64_t));
b.uniq_n = (int64_t*)ray_sys_alloc((size_t)n_part * sizeof(int64_t));
b.tab_off = (int64_t*)ray_sys_alloc((size_t)(n_part + 1) * sizeof(int64_t));
bool ok = b.miss && b.uniq && b.part_off && b.uniq_n && b.tab_off;
if (ok) {
memset(b.part_off, 0, (size_t)(n_part + 1) * sizeof(int64_t));
for (int64_t i = 0; i < n; i++)
if (out_pos[i] < 0) b.part_off[(hashes[i] >> b.part_shift) + 1]++;
for (int p = 0; p < n_part; p++) b.part_off[p + 1] += b.part_off[p];
{
int64_t* fill = b.uniq_n;
memcpy(fill, b.part_off, (size_t)n_part * sizeof(int64_t));
for (int64_t i = 0; i < n; i++)
if (out_pos[i] < 0) b.miss[fill[hashes[i] >> b.part_shift]++] = i;
}
int64_t tcap = 0;
for (int p = 0; p < n_part; p++) {
int64_t cnt = b.part_off[p + 1] - b.part_off[p];
int64_t cap = 0;
if (cnt > 0) { cap = 16; while (cnt * 2 > cap) cap <<= 1; }
b.tab_off[p] = tcap;
tcap += cap;
}
b.tab_off[n_part] = tcap;
b.tab = (int64_t*)ray_sys_alloc((size_t)(tcap > 0 ? tcap : 1) * sizeof(int64_t));
if (!b.tab) ok = false;
else {
dom_batch_cancel_for_test(RAY_SYM_DOMAIN_CANCEL_DEDUPE);
if (par) ray_pool_dispatch_n(pool, dom_batch_dedupe_fn, &b, (uint32_t)n_part);
else dom_batch_dedupe_fn(&b, 0, 0, n_part);
if (dom_batch_cancelled(pool)) ok = false;
}
ray_profile_tick("dom batch: misses deduped");
}
dom_lock();
dom->batch_inflight--;
if (dom_batch_cancelled(pool)) {
dom_unlock();
ray_sys_free(b.miss);
ray_sys_free(b.uniq);
ray_sys_free(b.part_off);
ray_sys_free(b.uniq_n);
ray_sys_free(b.tab_off);
ray_sys_free(b.tab);
return false;
}
bool indexed = dom->buckets || dom_build_index_locked(dom, n + 1);
if (!indexed) ok = false;
bool unchanged = ok && dom->buckets == b.buckets &&
atomic_load_explicit(&dom->count, memory_order_relaxed) == b.count &&
!atomic_load_explicit(&b.unsure, memory_order_relaxed);
if (ok && !unchanged) {
int64_t total = 0;
for (int p = 0; p < n_part; p++) {
int64_t lo = b.part_off[p], keep = 0;
for (int64_t j = 0; j < b.uniq_n[p]; j++) {
int64_t i = b.uniq[lo+j];
int64_t pos = dom_probe_locked(dom, hashes[i], strs[i], lens[i]);
if (pos >= 0) out_pos[i] = pos;
else b.uniq[lo+keep++] = i;
}
b.uniq_n[p] = keep; total += keep;
}
b.count = atomic_load_explicit(&dom->count, memory_order_relaxed);
if ((double)(b.count + total) > 0.7 * (double)(dom->bucket_mask + 1))
ok = dom_build_index_locked(dom, total);
b.buckets = dom->buckets; b.mask = dom->bucket_mask;
unchanged = ok;
}
if (unchanged) {
int64_t total = 0;
for (int p = 0; p < n_part; p++) total += b.uniq_n[p];
int64_t base = b.count;
ok = base + total < (int64_t)UINT32_MAX;
if (ok) ok = dom_grow_locked(dom, base + total);
for (int64_t k = base >> DOM_SLOT_LOG;
ok && total > 0 && k <= (base + total - 1) >> DOM_SLOT_LOG; k++)
ok = dom_chunk_locked(dom, k << DOM_SLOT_LOG) != NULL;
if (ok && total > 0) {
b.ui = (int64_t*)ray_sys_alloc((size_t)total * 2 * sizeof(int64_t));
if (!b.ui) ok = false;
else b.uoff = b.ui + total;
}
if (ok) {
int64_t pos = base;
size_t bytes = 0;
for (int64_t i = 0; i < n; i++) {
if (out_pos[i] != -1) continue;
int64_t k = pos - base;
if (k < total) { b.ui[k] = i; b.uoff[k] = (int64_t)bytes; }
bytes += ray_arena_str_bytes(lens[i]);
out_pos[i] = pos++;
}
if (pos - base != total) ok = false;
if (ok && bytes > 0) {
b.region = (char*)ray_arena_alloc_raw(dom->arena, bytes);
if (!b.region) ok = false;
}
if (ok) {
b.base = base;
b.atoms_w = atomic_load_explicit(&dom->atoms, memory_order_relaxed);
dom_batch_cancel_for_test(RAY_SYM_DOMAIN_CANCEL_INSERT);
if (total > 0) {
int64_t n_tasks = (total + DOM_INSERT_GRAIN - 1) / DOM_INSERT_GRAIN;
b.insert_total = total;
void* done_mem = NULL;
if (par && ray_pool_par_dispatch_ok(pool, total, DOM_INSERT_GRAIN) &&
n_tasks <= RAY_POOL_MAX_TASKS)
done_mem = ray_sys_alloc((size_t)n_tasks);
b.done = done_mem;
if (done_mem) {
memset(done_mem, 0, (size_t)n_tasks);
ray_pool_dispatch_n(pool, dom_batch_insert_task_fn, &b, (uint32_t)n_tasks);
for (int64_t t = 0; t < n_tasks; t++)
if (!atomic_load_explicit(&b.done[t], memory_order_acquire))
dom_batch_insert_task_fn(&b, 0, t, t + 1);
ray_sys_free(done_mem);
b.done = NULL;
} else {
dom_batch_insert_fn(&b, 0, 0, total);
}
}
if (par && ray_pool_par_dispatch_ok(pool, n_miss, 4096)) {
ray_pool_dispatch(pool, dom_batch_repeat_fn, &b, n_miss);
if (dom_batch_cancelled(pool)) dom_batch_repeat_fn(&b, 0, 0, n_miss);
} else {
dom_batch_repeat_fn(&b, 0, 0, n_miss);
}
ray_profile_tick("dom batch: inserted");
atomic_store_explicit(&dom->count, pos, memory_order_release);
int64_t* lut = atomic_load_explicit(&dom->runtime_lut, memory_order_relaxed);
if (lut) {
if (!dom_retire(dom, lut)) {
fprintf(stderr, "rayforce: sym domain '%s': OOM retiring runtime "
"LUT after batch append\n", dom->path ? dom->path : "?");
abort();
}
atomic_store_explicit(&dom->runtime_lut, NULL, memory_order_release);
}
} else {
for (int64_t i = 0; i < n; i++)
if (out_pos[i] < -1 || out_pos[i] >= base) out_pos[i] = -1;
}
}
} else {
for (int64_t i = 0; i < n; i++) if (out_pos[i] < -1) out_pos[i] = -1;
}
if (indexed && (!unchanged || !ok))
ok = dom_batch_append_serial_locked(dom, n, strs, lens, hashes, out_pos);
dom_unlock();
ray_profile_tick("dom batch: done");
ray_sys_free(b.miss);
ray_sys_free(b.uniq);
ray_sys_free(b.part_off);
ray_sys_free(b.uniq_n);
ray_sys_free(b.tab_off);
ray_sys_free(b.tab);
ray_sys_free(b.ui);
return ok;
}
bool ray_sym_domain_intern_batch64(ray_sym_domain_t* dom, int64_t n,
const char* const* strs, const size_t* lens,
const uint64_t* hashes, int64_t* out_pos) {
if (!dom || n < 0) return false;
if (dom->kind == DOM_IMPORT)
return n == 0 || ray_symimp_intern_batch(dom->imp, n, strs, lens, hashes, out_pos);
uint32_t h32[4096];
for (int64_t o = 0; o < n; o += 4096) {
int64_t k = n - o < 4096 ? n - o : 4096;
for (int64_t i = 0; i < k; i++) h32[i] = (uint32_t)hashes[o + i];
if (!ray_sym_domain_intern_batch(dom, k, strs + o, lens + o, h32, out_pos + o)) return false;
}
return true;
}
int64_t ray_sym_domain_count(ray_sym_domain_t* dom) {
if (!dom) return 0;
if (dom->kind == DOM_RUNTIME) return (int64_t)ray_sym_count();
return atomic_load_explicit(&dom->count, memory_order_acquire);
}
const char* ray_sym_domain_path(ray_sym_domain_t* dom) {
if (!dom || dom->kind == DOM_RUNTIME) return NULL;
return dom->path;
}
static const uint8_t* dom_rec_bytes(const ray_sym_domain_t* dom, const char* s, size_t slen,
uint8_t** buf, size_t* cap, size_t* rlen) {
if (!dom->z) { *rlen = slen; return (const uint8_t*)s; }
uint8_t* out = dom_buf_need(buf, cap, 2 * slen + 8);
if (!out) return NULL;
*rlen = (size_t)ray_fsst_encode_ix(dom->z, dom->zix, (const uint8_t*)s, (uint32_t)slen, out);
return out;
}
static void dom_flush_index(ray_sym_domain_t* dom, int64_t count, uint64_t end) {
dom_lock();
bool want = end >= dom_ix_min();
int64_t nb = dom->blk ? dom->blk->n : 0, n = nb + dom->wck.n;
int64_t base = dom->base_count;
const dom_ixb_t* bl = dom->blk ? dom->blk->ib : NULL;
uint64_t* all = want && n == dom_ck_count(count) && (base == 0 || bl)
? (uint64_t*)ray_sys_alloc((size_t)n * sizeof(uint64_t)) : NULL;
uint16_t* tl = all && count > base ? (uint16_t*)ray_sys_alloc((size_t)(count - base) * 2) : NULL;
if (all && count > base && !tl) { ray_sys_free(all); all = NULL; }
if (all) {
for (int64_t k = 0; k < nb; k++) all[k] = dom->blk->ib[k].off;
if (dom->wck.n) memcpy(all + nb, dom->wck.v, (size_t)dom->wck.n * sizeof(uint64_t));
dom_dir_t* dir = atomic_load_explicit(&dom->atoms, memory_order_relaxed);
for (int64_t i = base; i < count; i++) {
ray_t* a = dom_slot_get(dir, i);
size_t l = a ? ray_str_len(a) : 0;
tl[i - base] = l < DOM_LEN_LONG ? (uint16_t)l : (uint16_t)DOM_LEN_LONG;
}
}
int64_t ixc = dom->ix_count;
uint64_t ixe = dom->ix_end;
dom_unlock();
if (!all) return;
dom_lsrc_t ls = { bl, base, tl };
dom_ix_refresh(dom->path, all, n, &ls, count, end, ixc, ixe, true);
ray_sys_free(all);
ray_sys_free(tl);
dom_lock();
dom->ix_count = count;
dom->ix_end = end;
dom_unlock();
}
ray_err_t ray_sym_domain_flush(ray_sym_domain_t* dom, bool durable) {
if (!dom) return RAY_ERR_TYPE;
if (dom->kind == DOM_RUNTIME) return RAY_OK;
if (dom->kind == DOM_IMPORT) return ray_symimp_sync(dom->imp, durable);
if (!dom->path) return RAY_ERR_DOMAIN;
dom_lock();
bool dirty = atomic_load_explicit(&dom->count, memory_order_relaxed) != dom->disk_count;
dom_unlock();
if (!dirty) return RAY_OK;
char lock_path[1024];
char tmp_path[1024];
if (snprintf(lock_path, sizeof(lock_path), "%s.lk", dom->path) >= (int)sizeof(lock_path))
return RAY_ERR_IO;
if (snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", dom->path) >= (int)sizeof(tmp_path))
return RAY_ERR_IO;
ray_fd_t lock_fd = ray_file_open(lock_path,
RAY_OPEN_READ | RAY_OPEN_WRITE | RAY_OPEN_CREATE);
if (lock_fd == RAY_FD_INVALID) return RAY_ERR_IO;
ray_err_t err = ray_file_lock_ex(lock_fd);
if (err != RAY_OK) { ray_file_close(lock_fd); return err; }
dom_lock();
int64_t count = atomic_load_explicit(&dom->count, memory_order_relaxed);
int64_t disk_count = dom->disk_count;
size_t disk_size = dom->disk_size;
int64_t base_count = dom->base_count;
const uint8_t* bmap = (const uint8_t*)dom->map;
size_t bsize = dom->map_size;
dom_dir_t* dir = atomic_load_explicit(&dom->atoms, memory_order_relaxed);
dom_unlock();
if (count == disk_count) {
ray_file_unlock(lock_fd);
ray_file_close(lock_fd);
return RAY_OK;
}
{
struct stat st;
bool exists = (stat(dom->path, &st) == 0);
if (disk_count == 0) {
if (exists && st.st_size > 0) {
ray_file_unlock(lock_fd);
ray_file_close(lock_fd);
return RAY_ERR_CORRUPT;
}
ray_sym_domain_index_remove(dom->path);
} else {
bool ok = exists && (size_t)st.st_size == disk_size;
if (ok) {
FILE* vf = fopen(dom->path, "rb");
uint32_t magic = 0;
int64_t fcount = -1;
if (!vf || fread(&magic, 4, 1, vf) != 1 ||
fread(&fcount, 8, 1, vf) != 1 ||
magic != (dom->z ? DOMAIN_STRZ_MAGIC : DOMAIN_STRL_MAGIC) ||
fcount != disk_count)
ok = false;
if (vf) fclose(vf);
}
if (!ok) {
ray_file_unlock(lock_fd);
ray_file_close(lock_fd);
return RAY_ERR_CORRUPT;
}
}
}
size_t written_size = 0;
dom_ck_t tail = {0};
{
FILE* f = fopen(tmp_path, "wb");
if (!f) {
ray_file_unlock(lock_fd);
ray_file_close(lock_fd);
return RAY_ERR_IO;
}
uint32_t magic = dom->z && base_count > 0 ? DOMAIN_STRZ_MAGIC : DOMAIN_STRL_MAGIC;
err = RAY_OK;
if (fwrite(&magic, 4, 1, f) != 1 || fwrite(&count, 8, 1, f) != 1)
err = RAY_ERR_IO;
written_size = 12;
if (err == RAY_OK && base_count > 0) {
if (fwrite(bmap + 12, 1, bsize - 12, f) != bsize - 12) err = RAY_ERR_IO;
written_size = bsize;
}
enum { FLUSH_BUF = 1u << 20 };
uint8_t* wb = (uint8_t*)ray_sys_alloc(FLUSH_BUF);
uint8_t* zb = NULL;
size_t zc = 0, wn = 0;
if (!wb) err = RAY_ERR_OOM;
for (int64_t i = base_count; err == RAY_OK && i < count; i++) {
ray_t* s = dom_slot_get(dir, i);
if (!s) { err = RAY_ERR_CORRUPT; break; }
if (!(i % DOM_IX_STRIDE) && !dom_ck_push(&tail, written_size)) {
err = RAY_ERR_OOM;
break;
}
size_t slen = ray_str_len(s);
if (slen > UINT32_MAX) { err = RAY_ERR_RANGE; break; }
const uint8_t* rb = dom_rec_bytes(dom, ray_str_ptr(s), slen, &zb, &zc, &slen);
if (!rb) { err = RAY_ERR_OOM; break; }
uint32_t len32 = (uint32_t)slen;
if (wn + 4 + slen > FLUSH_BUF) {
if (wn && fwrite(wb, 1, wn, f) != wn) { err = RAY_ERR_IO; break; }
wn = 0;
}
if (4 + slen > FLUSH_BUF) {
if (fwrite(&len32, 4, 1, f) != 1 ||
fwrite(rb, 1, slen, f) != slen) {
err = RAY_ERR_IO;
break;
}
} else {
memcpy(wb + wn, &len32, 4);
if (slen) memcpy(wb + wn + 4, rb, slen);
wn += 4 + slen;
}
written_size += 4 + slen;
}
if (err == RAY_OK && wn && fwrite(wb, 1, wn, f) != wn) err = RAY_ERR_IO;
ray_sys_free(wb);
ray_sys_free(zb);
if (fclose(f) != 0 && err == RAY_OK) err = RAY_ERR_IO;
}
if (err != RAY_OK) goto fail_tmp;
if (durable) {
ray_fd_t tmp_fd = ray_file_open(tmp_path, RAY_OPEN_READ | RAY_OPEN_WRITE);
if (tmp_fd == RAY_FD_INVALID) { err = RAY_ERR_IO; goto fail_tmp; }
err = ray_file_sync(tmp_fd);
ray_file_close(tmp_fd);
if (err != RAY_OK) goto fail_tmp;
}
err = ray_file_rename(tmp_path, dom->path);
if (err != RAY_OK) goto fail_tmp;
dom_lock();
dom->disk_count = count;
dom->disk_size = written_size;
dom_ck_free(&dom->wck);
dom->wck = tail;
dom_unlock();
tail.v = NULL;
if (durable) {
err = ray_file_sync_dir(dom->path);
if (err != RAY_OK) {
ray_file_unlock(lock_fd);
ray_file_close(lock_fd);
return err;
}
}
dom_flush_index(dom, count, written_size);
ray_file_unlock(lock_fd);
ray_file_close(lock_fd);
return RAY_OK;
fail_tmp:
dom_ck_free(&tail);
remove(tmp_path);
ray_file_unlock(lock_fd);
ray_file_close(lock_fd);
return err;
}
static bool dom_fseek(FILE* f, int64_t off) {
#ifdef RAY_OS_WINDOWS
return _fseeki64(f, off, SEEK_SET) == 0;
#else
return fseeko(f, (off_t)off, SEEK_SET) == 0;
#endif
}
ray_err_t ray_sym_domain_flush_append(ray_sym_domain_t* dom, bool durable) {
if (!dom) return RAY_ERR_TYPE;
if (dom->kind == DOM_RUNTIME) return RAY_OK;
if (dom->kind == DOM_IMPORT) return ray_symimp_sync(dom->imp, durable);
if (!dom->path) return RAY_ERR_DOMAIN;
dom_lock();
bool dirty = atomic_load_explicit(&dom->count, memory_order_relaxed) != dom->disk_count;
dom_unlock();
if (!dirty && !durable) return RAY_OK;
char lock_path[1024];
if (snprintf(lock_path, sizeof(lock_path), "%s.lk", dom->path) >= (int)sizeof(lock_path))
return RAY_ERR_IO;
ray_fd_t lock_fd = ray_file_open(lock_path,
RAY_OPEN_READ | RAY_OPEN_WRITE | RAY_OPEN_CREATE);
if (lock_fd == RAY_FD_INVALID) return RAY_ERR_IO;
ray_err_t err = ray_file_lock_ex(lock_fd);
if (err != RAY_OK) { ray_file_close(lock_fd); return err; }
dom_lock();
int64_t count = atomic_load_explicit(&dom->count, memory_order_relaxed);
int64_t disk_count = dom->disk_count;
size_t disk_size = dom->disk_size;
dom_dir_t* dir = atomic_load_explicit(&dom->atoms, memory_order_relaxed);
dom_unlock();
if (count == disk_count && !durable) {
ray_file_unlock(lock_fd);
ray_file_close(lock_fd);
return RAY_OK;
}
struct stat st;
bool exists = stat(dom->path, &st) == 0;
if (disk_count == 0 ? (exists && st.st_size > 0)
: (!exists || (size_t)st.st_size != disk_size)) {
ray_file_unlock(lock_fd);
ray_file_close(lock_fd);
return RAY_ERR_CORRUPT;
}
if (disk_count == 0) ray_sym_domain_index_remove(dom->path);
FILE* f = fopen(dom->path, disk_count == 0 ? "wb+" : "rb+");
if (!f) { ray_file_unlock(lock_fd); ray_file_close(lock_fd); return RAY_ERR_IO; }
size_t size = disk_size;
uint32_t magic = DOMAIN_STRL_MAGIC;
if (disk_count == 0) {
int64_t zero = 0;
if (fwrite(&magic, 4, 1, f) != 1 || fwrite(&zero, 8, 1, f) != 1) err = RAY_ERR_IO;
size = 12;
} else if (!dom_fseek(f, (int64_t)disk_size)) {
err = RAY_ERR_IO;
}
dom_ck_t tail = {0};
enum { FLUSH_BUF = 1u << 20 };
uint8_t* wb = err == RAY_OK ? (uint8_t*)ray_sys_alloc(FLUSH_BUF) : NULL;
uint8_t* zb = NULL;
size_t zc = 0, wn = 0;
if (err == RAY_OK && !wb) err = RAY_ERR_OOM;
for (int64_t i = disk_count; err == RAY_OK && i < count; i++) {
ray_t* s = dom_slot_get(dir, i);
if (!s) { err = RAY_ERR_CORRUPT; break; }
if (!(i % DOM_IX_STRIDE) && !dom_ck_push(&tail, size)) { err = RAY_ERR_OOM; break; }
size_t slen = ray_str_len(s);
if (slen > UINT32_MAX) { err = RAY_ERR_RANGE; break; }
const uint8_t* rb = dom_rec_bytes(dom, ray_str_ptr(s), slen, &zb, &zc, &slen);
if (!rb) { err = RAY_ERR_OOM; break; }
uint32_t len32 = (uint32_t)slen;
if (wn + 4 + slen > FLUSH_BUF) {
if (wn && fwrite(wb, 1, wn, f) != wn) { err = RAY_ERR_IO; break; }
wn = 0;
}
if (4 + slen > FLUSH_BUF) {
if (fwrite(&len32, 4, 1, f) != 1 || fwrite(rb, 1, slen, f) != slen) {
err = RAY_ERR_IO;
break;
}
} else {
memcpy(wb + wn, &len32, 4);
if (slen) memcpy(wb + wn + 4, rb, slen);
wn += 4 + slen;
}
size += 4 + slen;
}
if (err == RAY_OK && wn && fwrite(wb, 1, wn, f) != wn) err = RAY_ERR_IO;
ray_sys_free(wb);
ray_sys_free(zb);
if (err == RAY_OK && (fflush(f) != 0 || !dom_fseek(f, 4) ||
fwrite(&count, 8, 1, f) != 1 || fflush(f) != 0))
err = RAY_ERR_IO;
if (err == RAY_OK && !durable)
ray_file_writeback_start(fileno(f), (int64_t)disk_size, (int64_t)(size - disk_size));
if (fclose(f) != 0 && err == RAY_OK) err = RAY_ERR_IO;
if (err == RAY_OK && durable) {
ray_fd_t fd = ray_file_open(dom->path, RAY_OPEN_READ | RAY_OPEN_WRITE);
if (fd == RAY_FD_INVALID) err = RAY_ERR_IO;
else { err = ray_file_sync(fd); ray_file_close(fd); }
if (err == RAY_OK) err = ray_file_sync_dir(dom->path);
}
if (err == RAY_OK) {
dom_lock();
dom->disk_count = count;
dom->disk_size = size;
for (int64_t k = 0; err == RAY_OK && k < tail.n; k++)
if (!dom_ck_push(&dom->wck, tail.v[k])) err = RAY_ERR_OOM;
if (err != RAY_OK) dom_ck_free(&dom->wck);
dom_unlock();
if (err == RAY_OK) dom_flush_index(dom, count, size);
err = RAY_OK;
}
dom_ck_free(&tail);
ray_file_unlock(lock_fd);
ray_file_close(lock_fd);
return err;
}
typedef struct {
const uint8_t* map;
const dom_ixb_t* ib;
int64_t n, count, k0, cb;
uint64_t end;
const ray_fsst_table_t* t;
const ray_fsst_index_t* ix;
uint8_t** out;
size_t* cap;
size_t* olen;
uint64_t** rel;
_Atomic(bool) oom;
} dom_zc_ctx_t;
static void dom_zc_chunk_fn(void* vctx, uint32_t wid, int64_t c0, int64_t c1) {
(void)wid;
dom_zc_ctx_t* x = (dom_zc_ctx_t*)vctx;
for (int64_t c = c0; c < c1; c++) {
int64_t ka = x->k0 + c * x->cb, kb = ka + x->cb < x->n ? ka + x->cb : x->n;
uint64_t lo = x->ib[ka].off, hi = kb < x->n ? x->ib[kb].off : x->end;
uint8_t* out = dom_buf_need(&x->out[c], &x->cap[c], 2 * (size_t)(hi - lo) + 16);
if (!out) { atomic_store_explicit(&x->oom, true, memory_order_relaxed); return; }
const uint8_t* p = x->map + lo;
size_t o = 0;
int64_t ie = kb * DOM_IX_STRIDE < x->count ? kb * DOM_IX_STRIDE : x->count;
for (int64_t i = ka * DOM_IX_STRIDE; i < ie; i++) {
if (!(i % DOM_IX_STRIDE)) x->rel[c][i / DOM_IX_STRIDE - ka] = o;
uint32_t l = dom_rd32(p);
uint32_t cl = (uint32_t)ray_fsst_encode_ix(x->t, x->ix, p + 4, l, out + o + 4);
memcpy(out + o, &cl, 4);
o += 4 + cl;
p += 4 + l;
}
x->olen[c] = o;
}
}
ray_err_t ray_sym_domain_compress(const char* path) {
if (!path) return RAY_ERR_TYPE;
char* rpath = dom_resolve_path(path);
if (!rpath) return RAY_ERR_IO;
dom_lock();
for (ray_sym_domain_t* d = g_domains; d; d = d->next)
if (dom_path_eq(d->path, rpath)) { dom_unlock(); ray_sys_free(rpath); return RAY_ERR_DOMAIN; }
dom_unlock();
size_t size = 0;
ray_file_id_t id;
const uint8_t* map = (const uint8_t*)ray_vm_map_file_id(rpath, &size, &id);
int64_t count = -1;
if (map && size >= 12 && dom_rd32(map) == DOMAIN_STRL_MAGIC) memcpy(&count, map + 4, 8);
if (count <= 0 || count > (int64_t)UINT32_MAX) {
if (map) ray_vm_unmap_file((void*)map, size);
ray_sys_free(rpath);
return RAY_OK;
}
ray_vm_advise_seq((void*)map, size);
dom_ixh_t hx;
void* ixm = NULL;
size_t ixs = 0;
dom_blk_t* b = dom_ix_load(rpath, map, size, count, &id, &ixm, &ixs, &hx);
if (!b) {
b = dom_blk_new(count, true);
size_t o = 12;
dom_src_t src;
if (b && !dom_src_open(&src, rpath, &id, size)) src = dom_src_map(map, size);
bool walked = b && dom_blk_walk_src(&src, b, 0, &o, NULL) && o == size;
if (b) dom_src_close(&src);
if (!walked) {
ray_sys_free(b);
ray_vm_unmap_file((void*)map, size);
ray_sys_free(rpath);
return b ? RAY_ERR_IO : RAY_ERR_OOM;
}
b->end = size;
}
ray_err_t err = RAY_OK;
int64_t n = b->n;
int64_t sb = (int64_t)((size - 12) >> 20);
if (sb < 1) sb = 1;
int64_t sn = 0, scap = ((n + sb - 1) / sb) * DOM_IX_STRIDE;
const uint8_t** sp = (const uint8_t**)ray_sys_alloc((size_t)scap * sizeof(*sp));
uint32_t* sl = (uint32_t*)ray_sys_alloc((size_t)scap * sizeof(*sl));
ray_fsst_table_t* t = (ray_fsst_table_t*)ray_sys_alloc(sizeof(*t));
ray_fsst_index_t* tix = (ray_fsst_index_t*)ray_sys_alloc(sizeof(*tix));
if (!sp || !sl || !t || !tix) err = RAY_ERR_OOM;
for (int64_t k = 0; err == RAY_OK && k < n; k += sb) {
const uint8_t* p = map + b->ib[k].off;
int64_t ie = (k + 1) * DOM_IX_STRIDE < count ? (k + 1) * DOM_IX_STRIDE : count;
for (int64_t i = k * DOM_IX_STRIDE; i < ie; i++) {
sl[sn] = dom_rd32(p);
sp[sn++] = p + 4;
p += 4 + (size_t)dom_rd32(p);
}
}
int tr = err == RAY_OK ? ray_fsst_train(sp, sl, sn, t) : 0;
ray_sys_free(sp);
ray_sys_free(sl);
if (err == RAY_OK && tr < 0) err = RAY_ERR_OOM;
bool code = err == RAY_OK && tr > 0;
char tp[PATH_MAX];
uint64_t* ck = code ? (uint64_t*)ray_sys_alloc((size_t)n * sizeof(uint64_t)) : NULL;
if (code && (!ck || snprintf(tp, sizeof(tp), "%s.z.%llu.tmp", rpath, dom_pid()) >= (int)sizeof(tp)))
err = ck ? RAY_ERR_IO : RAY_ERR_OOM;
FILE* f = code && err == RAY_OK ? fopen(tp, "wb") : NULL;
if (code && err == RAY_OK && !f) err = RAY_ERR_IO;
uint64_t pos = DOM_Z_REC0;
if (f) {
ray_fsst_index_build(t, tix);
uint32_t magic = DOMAIN_STRZ_MAGIC;
if (fwrite(&magic, 4, 1, f) != 1 || fwrite(&count, 8, 1, f) != 1 ||
fwrite(t, 1, DOM_Z_TAB, f) != DOM_Z_TAB)
err = RAY_ERR_IO;
ray_pool_t* pool = ray_pool_get();
uint32_t nw = pool ? ray_pool_total_workers(pool) : 1;
int64_t cb = (int64_t)(((uint64_t)(2u << 20) * (uint64_t)n) / (size - 12));
if (cb < 1) cb = 1;
int64_t wn = 2 * (int64_t)nw < 64 ? 2 * (int64_t)nw : 64;
dom_zc_ctx_t x;
memset(&x, 0, sizeof(x));
x.map = map; x.ib = b->ib; x.n = n; x.count = count; x.cb = cb; x.end = size;
x.t = t; x.ix = tix;
x.out = (uint8_t**)ray_sys_alloc((size_t)wn * sizeof(uint8_t*));
x.cap = (size_t*)ray_sys_alloc((size_t)wn * sizeof(size_t));
x.olen = (size_t*)ray_sys_alloc((size_t)wn * sizeof(size_t));
x.rel = (uint64_t**)ray_sys_alloc((size_t)wn * sizeof(uint64_t*));
if (!x.out || !x.cap || !x.olen || !x.rel) err = RAY_ERR_OOM;
for (int64_t c = 0; err == RAY_OK && c < wn; c++)
if (!(x.rel[c] = (uint64_t*)ray_sys_alloc((size_t)cb * sizeof(uint64_t)))) err = RAY_ERR_OOM;
for (int64_t k0 = 0; err == RAY_OK && k0 < n; k0 += wn * cb) {
int64_t nc = (n - k0 + cb - 1) / cb;
if (nc > wn) nc = wn;
x.k0 = k0;
if (pool && nc > 1) ray_pool_dispatch_n(pool, dom_zc_chunk_fn, &x, (uint32_t)nc);
else dom_zc_chunk_fn(&x, 0, 0, nc);
if (atomic_load_explicit(&x.oom, memory_order_relaxed)) { err = RAY_ERR_OOM; break; }
for (int64_t c = 0; c < nc; c++) {
int64_t ka = k0 + c * cb, kb = ka + cb < n ? ka + cb : n;
for (int64_t k = ka; k < kb; k++) ck[k] = pos + x.rel[c][k - ka];
if (fwrite(x.out[c], 1, x.olen[c], f) != x.olen[c]) { err = RAY_ERR_IO; break; }
pos += x.olen[c];
}
}
for (int64_t c = 0; x.out && c < wn; c++) ray_sys_free(x.out[c]);
for (int64_t c = 0; x.rel && c < wn; c++) ray_sys_free(x.rel[c]);
ray_sys_free(x.out); ray_sys_free(x.cap); ray_sys_free(x.olen); ray_sys_free(x.rel);
if (fclose(f) != 0 && err == RAY_OK) err = RAY_ERR_IO;
if (err == RAY_OK) ray_sym_domain_index_remove(rpath);
if (err == RAY_OK) err = ray_file_rename(tp, rpath);
if (err != RAY_OK) remove(tp);
}
if (code && err == RAY_OK && pos >= dom_ix_min()) {
size_t zsize = 0;
ray_file_id_t zid;
const uint8_t* zmap = (const uint8_t*)ray_vm_map_file_id(rpath, &zsize, &zid);
if (zmap) {
dom_lsrc_t ls = { b->ib, count, NULL };
dom_ix_save(rpath, zmap, zsize, ck, n, &ls, count, zsize, &zid);
ray_vm_unmap_file((void*)zmap, zsize);
}
}
ray_sys_free(ck);
ray_sys_free(t);
ray_sys_free(tix);
ray_sys_free(b);
if (ixm) ray_vm_unmap_file(ixm, ixs);
ray_vm_unmap_file((void*)map, size);
ray_sys_free(rpath);
return err;
}
uint8_t ray_g_sym_audit = 0;
void ray_sym_audit_cell(ray_t* vec, int64_t row, int64_t pos, ray_t* resolved) {
ray_sym_domain_t* dom = ray_sym_vec_domain(vec);
int64_t count = ray_sym_domain_count(dom);
if (resolved != NULL && pos >= 0 && pos < count) return;
fprintf(stderr,
"rayforce: RAY_SYM_AUDIT violation: vec=%p type=%d attrs=0x%02x "
"len=%lld row=%lld pos=%lld domain=%p (%s) count=%lld resolved=%p\n",
(void*)vec, (int)vec->type, (unsigned)vec->attrs,
(long long)vec->len, (long long)row, (long long)pos,
(void*)dom,
dom == ray_sym_runtime_domain() ? "runtime"
: (ray_sym_domain_path(dom) ? ray_sym_domain_path(dom) : "file"),
(long long)count, (void*)resolved);
abort();
}