#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/fileio.h"
#include "ops/hash.h"
#include "core/pool.h"
#include "sym.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
#ifndef PATH_MAX
# define PATH_MAX 4096
#endif
#define DOMAIN_STRL_MAGIC 0x4C525453U
typedef enum {
DOM_RUNTIME = 0,
DOM_FILE = 1,
} 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;
typedef struct dom_raw_snap_s {
const unsigned char* map;
const size_t* offsets;
int64_t count;
} dom_raw_snap_t;
struct ray_sym_domain_s {
uint8_t kind;
bool immortal;
uint32_t rc;
char* path;
void* map;
size_t map_size;
int64_t base_count;
int64_t disk_count;
size_t disk_size;
_Atomic(int64_t) count;
_Atomic(ray_t**) atoms;
int64_t atoms_cap;
ray_arena_t* arena;
size_t* offsets;
_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_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_buckets_locked(ray_sym_domain_t* d, uint64_t* old) {
if (!old) return true;
if (d->batch_inflight == 0) { ray_sys_free(old); return true; }
return dom_retire(d, old);
}
static bool dom_publish_raw_snap(ray_sym_domain_t* d) {
dom_raw_snap_t* snap = NULL;
if (d->map && d->offsets && 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->offsets = d->offsets;
snap->count = d->base_count;
}
dom_raw_snap_t* old = atomic_exchange_explicit(&d->raw_snap, snap, memory_order_acq_rel);
if (old) (void)dom_retire(d, old);
return true;
}
static void dom_destroy(ray_sym_domain_t* d) {
ray_t** atoms = atomic_load_explicit(&d->atoms, memory_order_relaxed);
ray_sys_free(atoms);
if (d->arena) ray_arena_destroy(d->arena);
ray_sys_free(d->offsets);
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 bool dom_parse_strl_atoms(const void* map, size_t map_size,
ray_t*** out_atoms, size_t** out_offsets,
int64_t* out_count) {
const uint8_t* base = (const uint8_t*)map;
if (map_size < 12) return false;
uint32_t magic;
memcpy(&magic, base, 4);
if (magic != DOMAIN_STRL_MAGIC) return false;
int64_t count;
memcpy(&count, base + 4, 8);
if (count < 0 || count > UINT32_MAX) return false;
ray_t** atoms = NULL;
size_t* offsets = NULL;
if (count > 0) {
atoms = (ray_t**)ray_sys_alloc((size_t)((size_t)count) * (sizeof(ray_t*)));
if (!atoms) return false;
offsets = (size_t*)ray_sys_alloc((size_t)count * sizeof(size_t));
if (!offsets) { ray_sys_free(atoms); return false; }
}
size_t off = 12;
size_t remaining = map_size - 12;
for (int64_t i = 0; i < count; i++) {
offsets[i] = off;
uint32_t slen;
if (remaining < 4) goto fail;
memcpy(&slen, base + off, 4);
off += 4; remaining -= 4;
if ((size_t)slen > remaining) goto fail;
off += slen; remaining -= slen;
}
if (remaining != 0) goto fail;
*out_atoms = atoms;
*out_offsets = offsets;
*out_count = count;
return true;
fail:
ray_sys_free(atoms);
ray_sys_free(offsets);
return false;
}
static ray_t* dom_atom_at_locked(ray_sym_domain_t* d, int64_t pos) {
ray_t** atoms = atomic_load_explicit(&d->atoms, memory_order_relaxed);
ray_t* a = atomic_load_explicit((_Atomic(ray_t*)*)&atoms[pos],
memory_order_relaxed);
if (a) return a;
const uint8_t* base = (const uint8_t*)d->map;
size_t off = d->offsets[pos];
uint32_t slen;
memcpy(&slen, base + off, 4);
a = ray_arena_str(d->arena, (const char*)base + off + 4, slen);
if (!a) return NULL;
atomic_store_explicit((_Atomic(ray_t*)*)&atoms[pos], a, memory_order_release);
return a;
}
static bool dom_extend_from_file_locked(ray_sym_domain_t* d, size_t st_size) {
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;
void* map = ray_vm_map_file(d->path, &map_size);
if (!map) return false;
ray_t** fresh = NULL;
size_t* fresh_offsets = NULL;
int64_t fresh_count = 0;
if (!dom_parse_strl_atoms(map, map_size, &fresh, &fresh_offsets,
&fresh_count)) {
ray_vm_unmap_file(map, map_size);
return false;
}
bool ok = fresh_count >= count;
const uint8_t* nbase = (const uint8_t*)map;
const uint8_t* obase = (const uint8_t*)d->map;
#define DOM_REC(b, off, lp, pp) do { \
uint32_t _l; memcpy(&_l, (b) + (off), 4); \
(lp) = _l; (pp) = (const char*)((b) + (off) + 4); \
} while (0)
if (ok && count == 0 && fresh_count > 0) {
uint32_t l0; const char* p0; (void)p0;
DOM_REC(nbase, fresh_offsets[0], l0, p0);
if (l0 != 0) ok = false;
}
for (int64_t i = 0; ok && i < count; i++) {
uint32_t ol, nl; const char *op, *np;
DOM_REC(obase, d->offsets[i], ol, op);
DOM_REC(nbase, fresh_offsets[i], nl, np);
if (ol != nl || (ol > 0 && memcmp(op, np, ol) != 0)) ok = false;
}
#undef DOM_REC
if (ok && fresh_count > count) {
ray_t** atoms = atomic_load_explicit(&d->atoms, memory_order_relaxed);
ray_t** narr = (ray_t**)ray_sys_alloc((size_t)fresh_count * sizeof(ray_t*));
if (!narr) {
ok = false;
} else if (!dom_retire(d, atoms)) {
ray_sys_free(narr);
ok = false;
} else {
if (count > 0) memcpy(narr, atoms, (size_t)count * sizeof(ray_t*));
for (int64_t i = count; i < fresh_count; i++) narr[i] = NULL;
ray_sys_free(fresh);
(void)dom_retire(d, d->offsets);
d->offsets = fresh_offsets;
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;
atomic_store_explicit(&d->atoms, narr, memory_order_release);
d->atoms_cap = fresh_count;
atomic_store_explicit(&d->count, fresh_count, memory_order_release);
d->disk_count = fresh_count;
d->disk_size = st_size;
if (old_map) {
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; }
}
(void)dom_publish_raw_snap(d);
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) {
if (!dom_retire(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();
}
atomic_store_explicit(&d->runtime_lut, NULL, memory_order_release);
}
return true;
}
}
if (ok && fresh_count == count) d->disk_size = st_size;
ray_sys_free(fresh);
ray_sys_free(fresh_offsets);
ray_vm_unmap_file(map, map_size);
return ok;
}
static ray_sym_domain_t* dom_open_impl(const char* path, bool create) {
if (!path) return NULL;
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->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(rpath, &d->map_size);
ray_t** atoms = NULL;
size_t* offsets = NULL;
int64_t count = 0;
if (!d->map ||
!dom_parse_strl_atoms(d->map, d->map_size, &atoms, &offsets, &count)) {
dom_destroy(d);
return NULL;
}
atomic_store_explicit(&d->atoms, atoms, memory_order_relaxed);
atomic_store_explicit(&d->count, count, memory_order_relaxed);
d->offsets = offsets;
d->atoms_cap = count;
d->base_count = count;
(void)dom_publish_raw_snap(d);
d->disk_count = count;
d->disk_size = d->map_size;
if (count > 0) {
uint32_t l0;
memcpy(&l0, (const uint8_t*)d->map + offsets[0], 4);
if (l0 != 0) {
dom_destroy(d);
return NULL;
}
}
}
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_new(void) {
ray_sym_domain_t* d = ray_sys_alloc(sizeof(*d));
if (!d) return NULL;
d->kind = DOM_FILE;
d->rc = 1;
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);
int64_t count = atomic_load_explicit(&dom->count, memory_order_acquire);
if (pos < 0 || pos >= count) return NULL;
ray_t** atoms = atomic_load_explicit(&dom->atoms, memory_order_acquire);
ray_t* a = atomic_load_explicit((_Atomic(ray_t*)*)&atoms[pos],
memory_order_acquire);
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_RUNTIME) 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->offsets = snap->offsets;
out->count = snap->count;
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;
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; }
for (int64_t i = 0; i < count; i++) {
ray_t* a = dom_atom_at_locked(dom, i);
if (!a) { ray_sys_free(lut); dom_unlock(); return NULL; }
lut[i] = ray_sym_intern(ray_str_ptr(a), ray_str_len(a));
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 {
for (int64_t i = 0; i < count; i++) {
ray_t* a = dom_atom_at_locked(d, i);
if (!a) { ray_sys_free(buckets); return false; }
uint32_t h = (uint32_t)ray_hash_bytes(ray_str_ptr(a), ray_str_len(a));
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;
ray_t* a = dom_atom_at_locked(d, pos);
if (a && ray_str_len(a) == len &&
(len == 0 || memcmp(ray_str_ptr(a), 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 (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 (count >= d->atoms_cap) {
int64_t ncap = d->atoms_cap < 8 ? 8 : d->atoms_cap * 2;
ray_t** narr = (ray_t**)ray_sys_alloc((size_t)ncap * sizeof(ray_t*));
if (!narr) return -1;
ray_t** old = atomic_load_explicit(&d->atoms, memory_order_relaxed);
if (count > 0) memcpy(narr, old, (size_t)count * sizeof(ray_t*));
if (!dom_retire(d, old)) { ray_sys_free(narr); return -1; }
atomic_store_explicit(&d->atoms, narr, memory_order_release);
d->atoms_cap = ncap;
}
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;
ray_t** atoms = atomic_load_explicit(&d->atoms, memory_order_relaxed);
atoms[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);
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;
ray_t* const* 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;
ray_t** atoms_w;
_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 = atomic_load_explicit((_Atomic(ray_t*)*)&b->atoms[pos],
memory_order_acquire);
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]);
b->atoms_w[pos] = s;
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;
}
}
}
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 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 (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);
if (par) ray_pool_dispatch(pool, dom_batch_probe_fn, &b, n);
else dom_batch_probe_fn(&b, 0, 0, n);
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 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);
ray_profile_tick("dom batch: misses deduped");
}
dom_lock();
dom->batch_inflight--;
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 && base + total > dom->atoms_cap) {
int64_t ncap = dom->atoms_cap < 8 ? 8 : dom->atoms_cap;
while (ncap < base + total) ncap *= 2;
ray_t** narr = (ray_t**)ray_sys_alloc((size_t)ncap * sizeof(ray_t*));
if (!narr) ok = false;
else {
ray_t** old = atomic_load_explicit(&dom->atoms, memory_order_relaxed);
if (base > 0) memcpy(narr, old, (size_t)base * sizeof(ray_t*));
if (!dom_retire(dom, old)) { ray_sys_free(narr); ok = false; }
else {
atomic_store_explicit(&dom->atoms, narr, memory_order_release);
dom->atoms_cap = ncap;
}
}
}
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);
if (total > 0) {
if (par && ray_pool_par_dispatch_ok(pool, total, 4096))
ray_pool_dispatch(pool, dom_batch_insert_fn, &b, total);
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);
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 (!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;
}
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;
}
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->path) return RAY_ERR_DOMAIN;
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;
if (count != disk_count) {
for (int64_t i = 0; i < count; i++) {
if (!dom_atom_at_locked(dom, i)) { dom_unlock(); return RAY_ERR_OOM; }
}
}
ray_t** atoms = atomic_load_explicit(&dom->atoms, memory_order_relaxed);
dom_unlock();
if (count == disk_count) 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; }
{
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;
}
} 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 != 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;
{
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 = 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;
enum { FLUSH_BUF = 1u << 20 };
uint8_t* wb = (uint8_t*)ray_sys_alloc(FLUSH_BUF);
size_t wn = 0;
if (!wb) err = RAY_ERR_OOM;
for (int64_t i = 0; err == RAY_OK && i < count; i++) {
ray_t* s = atoms[i];
size_t slen = ray_str_len(s);
if (slen > UINT32_MAX) { err = RAY_ERR_RANGE; 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(ray_str_ptr(s), 1, slen, f) != slen) {
err = RAY_ERR_IO;
break;
}
} else {
memcpy(wb + wn, &len32, 4);
if (slen) memcpy(wb + wn + 4, ray_str_ptr(s), 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);
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;
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_lock();
dom->disk_count = count;
dom->disk_size = written_size;
dom_unlock();
ray_file_unlock(lock_fd);
ray_file_close(lock_fd);
return RAY_OK;
fail_tmp:
remove(tmp_path);
ray_file_unlock(lock_fd);
ray_file_close(lock_fd);
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();
}