#if defined(__APPLE__)
# define _DARWIN_C_SOURCE
#elif !defined(_WIN32)
# define _GNU_SOURCE
#endif
#include "heap.h"
#include "cow.h"
#include "sys.h"
#include "core/platform.h"
#include "core/timer.h"
#include "core/pool.h"
#include "core/runtime.h"
#include "table/sym.h"
#include "table/domain.h"
#include "lang/eval.h"
#include "store/hnsw.h"
#include "store/csr.h"
#include "store/part.h"
#include "ops/idxop.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdatomic.h>
#if defined(RAY_OS_WINDOWS)
# define RAY_HEAP_FILE_SPILL 0
#else
# define RAY_HEAP_FILE_SPILL 1
# include <sys/mman.h>
#endif
#ifdef DEBUG
#if !defined(RAY_OS_WINDOWS)
#include <execinfo.h>
#endif
#define DFD_CAP_BITS 20
#define DFD_CAP (1u << DFD_CAP_BITS)
#define DFD_TOMB ((const void*)(uintptr_t)1)
static const void* dfd_slots[DFD_CAP];
static atomic_flag dfd_lock_flag = ATOMIC_FLAG_INIT;
static bool dfd_enabled(void) {
static int on = -1;
if (on < 0) {
const char* e = getenv("RAY_DFD");
on = (e && *e && *e != '0') ? 1 : 0;
}
return on == 1;
}
static void dfd_lock(void) { while (atomic_flag_test_and_set_explicit(&dfd_lock_flag, memory_order_acquire)) {} }
static void dfd_unlock(void) { atomic_flag_clear_explicit(&dfd_lock_flag, memory_order_release); }
static inline uint32_t dfd_hash(const void* p) {
uint64_t x = (uint64_t)(uintptr_t)p >> 5;
x *= 0x9E3779B97F4A7C15ull;
return (uint32_t)(x >> (64 - DFD_CAP_BITS));
}
static void dfd_add(const void* p) {
if (!dfd_enabled()) return;
dfd_lock();
uint32_t i = dfd_hash(p);
uint32_t first_tomb = UINT32_MAX;
for (uint32_t n = 0; n < DFD_CAP; n++, i = (i + 1) & (DFD_CAP - 1)) {
if (dfd_slots[i] == p) { dfd_unlock(); return; }
if (dfd_slots[i] == DFD_TOMB && first_tomb == UINT32_MAX) first_tomb = i;
if (dfd_slots[i] == NULL) {
dfd_slots[first_tomb != UINT32_MAX ? first_tomb : i] = p;
dfd_unlock();
return;
}
}
dfd_unlock();
}
static void dfd_remove(const void* p) {
if (!dfd_enabled()) return;
dfd_lock();
uint32_t i = dfd_hash(p);
for (uint32_t n = 0; n < DFD_CAP; n++, i = (i + 1) & (DFD_CAP - 1)) {
if (dfd_slots[i] == p) { dfd_slots[i] = DFD_TOMB; break; }
if (dfd_slots[i] == NULL) break;
}
dfd_unlock();
}
static bool dfd_contains(const void* p) {
if (!dfd_enabled()) return false;
dfd_lock();
uint32_t i = dfd_hash(p);
bool found = false;
for (uint32_t n = 0; n < DFD_CAP; n++, i = (i + 1) & (DFD_CAP - 1)) {
if (dfd_slots[i] == p) { found = true; break; }
if (dfd_slots[i] == NULL) break;
}
dfd_unlock();
return found;
}
static void dfd_purge_range(uintptr_t lo, uintptr_t hi) {
if (!dfd_enabled()) return;
dfd_lock();
for (uint32_t i = 0; i < DFD_CAP; i++) {
uintptr_t p = (uintptr_t)dfd_slots[i];
if (p > (uintptr_t)DFD_TOMB && p >= lo && p < hi)
dfd_slots[i] = DFD_TOMB;
}
dfd_unlock();
}
static void dfd_report(const char* who, const void* p) {
fprintf(stderr, "\n=== DFD: %s on FREED block %p ===\n", who, p);
#if !defined(RAY_OS_WINDOWS)
void* frames[64];
int n = backtrace(frames, 64);
backtrace_symbols_fd(frames, n, 2);
#endif
fflush(stderr);
if (!getenv("RAY_DFD_NO_ABORT")) abort();
}
void ray_dfd_check_live(const void* p, const char* who);
void ray_dfd_check_live(const void* p, const char* who) {
if (p && dfd_contains(p)) dfd_report(who, p);
}
static void dfd_validate_freelists(void);
#else
#define dfd_add(p) ((void)0)
#define dfd_remove(p) ((void)0)
#define dfd_purge_range(lo, hi) ((void)0)
#endif
#if RAY_HEAP_FILE_SPILL
static int heap_preallocate(int fd, off_t offset, off_t len) {
#if defined(__APPLE__)
fstore_t fs = {
.fst_flags = F_ALLOCATECONTIG | F_ALLOCATEALL,
.fst_posmode = F_PEOFPOSMODE,
.fst_offset = 0,
.fst_length = offset + len,
.fst_bytesalloc = 0,
};
if (fcntl(fd, F_PREALLOCATE, &fs) == -1) {
fs.fst_flags = F_ALLOCATEALL;
if (fcntl(fd, F_PREALLOCATE, &fs) == -1) return errno ? errno : -1;
}
if (ftruncate(fd, offset + len) != 0) return errno ? errno : -1;
return 0;
#else
return posix_fallocate(fd, offset, len);
#endif
}
#endif
_Static_assert(sizeof(ray_pool_hdr_t) <= 16,
"ray_pool_hdr_t must fit in aux (16 bytes)");
RAY_TLS ray_heap_t* ray_tl_heap = NULL;
#if RAY_MEM_STATS
# define RAY_STAT(x) (x)
#else
# define RAY_STAT(x) ((void)0)
#endif
static _Atomic(uint64_t) g_heap_id_bitmap[RAY_HEAP_ID_WORDS] = { [0] = 1ULL };
static _Atomic(uint64_t) g_heap_id_cursor = 0;
static _Atomic(int64_t) g_direct_bytes = 0;
static _Atomic(int64_t) g_direct_count = 0;
static _Atomic(uint32_t) g_mem_trace_active = 0;
static _Atomic(uint64_t) g_mem_trace_alloc_count = 0;
static _Atomic(uint64_t) g_mem_trace_free_count = 0;
static _Atomic(uint64_t) g_mem_trace_allocated = 0;
static _Atomic(uint64_t) g_mem_trace_freed = 0;
static _Atomic(int64_t) g_mem_trace_live = 0;
static _Atomic(uint64_t) g_mem_trace_peak = 0;
static inline void mem_trace_note_alloc(size_t bytes) {
if (RAY_LIKELY(atomic_load_explicit(&g_mem_trace_active,
memory_order_relaxed) != 1)) return;
atomic_fetch_add_explicit(&g_mem_trace_alloc_count, 1, memory_order_relaxed);
atomic_fetch_add_explicit(&g_mem_trace_allocated, (uint64_t)bytes,
memory_order_relaxed);
int64_t live = atomic_fetch_add_explicit(&g_mem_trace_live, (int64_t)bytes,
memory_order_relaxed) + (int64_t)bytes;
uint64_t candidate = live > 0 ? (uint64_t)live : 0;
uint64_t peak = atomic_load_explicit(&g_mem_trace_peak, memory_order_relaxed);
while (candidate > peak &&
!atomic_compare_exchange_weak_explicit(&g_mem_trace_peak, &peak,
candidate,
memory_order_relaxed,
memory_order_relaxed)) {}
}
static inline void mem_trace_note_free(size_t bytes) {
if (RAY_LIKELY(atomic_load_explicit(&g_mem_trace_active,
memory_order_relaxed) != 1)) return;
atomic_fetch_add_explicit(&g_mem_trace_free_count, 1, memory_order_relaxed);
atomic_fetch_add_explicit(&g_mem_trace_freed, (uint64_t)bytes,
memory_order_relaxed);
atomic_fetch_sub_explicit(&g_mem_trace_live, (int64_t)bytes,
memory_order_relaxed);
}
bool ray_mem_trace_begin(void) {
uint32_t expected = 0;
if (!atomic_compare_exchange_strong_explicit(&g_mem_trace_active, &expected, 2,
memory_order_acq_rel,
memory_order_relaxed))
return false;
atomic_store_explicit(&g_mem_trace_alloc_count, 0, memory_order_relaxed);
atomic_store_explicit(&g_mem_trace_free_count, 0, memory_order_relaxed);
atomic_store_explicit(&g_mem_trace_allocated, 0, memory_order_relaxed);
atomic_store_explicit(&g_mem_trace_freed, 0, memory_order_relaxed);
atomic_store_explicit(&g_mem_trace_live, 0, memory_order_relaxed);
atomic_store_explicit(&g_mem_trace_peak, 0, memory_order_relaxed);
atomic_store_explicit(&g_mem_trace_active, 1, memory_order_release);
return true;
}
void ray_mem_trace_end(ray_mem_trace_t* out) {
atomic_store_explicit(&g_mem_trace_active, 2, memory_order_release);
if (out) {
out->alloc_count = atomic_load_explicit(&g_mem_trace_alloc_count,
memory_order_relaxed);
out->free_count = atomic_load_explicit(&g_mem_trace_free_count,
memory_order_relaxed);
out->allocated_bytes = atomic_load_explicit(&g_mem_trace_allocated,
memory_order_relaxed);
out->freed_bytes = atomic_load_explicit(&g_mem_trace_freed,
memory_order_relaxed);
out->net_bytes = atomic_load_explicit(&g_mem_trace_live,
memory_order_relaxed);
out->peak_live_bytes = atomic_load_explicit(&g_mem_trace_peak,
memory_order_relaxed);
}
atomic_store_explicit(&g_mem_trace_active, 0, memory_order_release);
}
static _Atomic(int64_t) g_anon_committed = 0;
static _Atomic(int64_t) g_anon_watermark = 0;
static _Atomic(int64_t) g_anon_peak = 0;
static _Atomic(int64_t) g_anon_maps = 0;
static _Atomic(int64_t) g_anon_large = 0;
static _Atomic(int64_t) g_anon_spills = 0;
static _Atomic(int64_t) g_anon_stmt_peak = 0;
static _Atomic(int64_t) g_spill_pools = 0;
static void heap_anon_spilled(void) {
atomic_fetch_add_explicit(&g_anon_spills, 1, memory_order_relaxed);
}
static void heap_anon_commit(int64_t bytes, bool mapped, bool large) {
if (mapped) atomic_fetch_add_explicit(&g_anon_maps, 1, memory_order_relaxed);
if (large) atomic_fetch_add_explicit(&g_anon_large, bytes, memory_order_relaxed);
int64_t nv = atomic_fetch_add_explicit(&g_anon_committed, bytes,
memory_order_relaxed) + bytes;
int64_t pk = atomic_load_explicit(&g_anon_peak, memory_order_relaxed);
while (nv > pk &&
!atomic_compare_exchange_weak_explicit(&g_anon_peak, &pk, nv,
memory_order_relaxed,
memory_order_relaxed)) {
}
pk = atomic_load_explicit(&g_anon_stmt_peak, memory_order_relaxed);
while (nv > pk &&
!atomic_compare_exchange_weak_explicit(&g_anon_stmt_peak, &pk, nv,
memory_order_relaxed,
memory_order_relaxed)) {}
}
static int64_t heap_anon_watermark(void) {
int64_t wm = atomic_load_explicit(&g_anon_watermark, memory_order_relaxed);
if (wm > 0) return wm;
int64_t lim = ray_sys_ram_limit();
if (lim <= 0) return lim;
int64_t room = lim / 4;
if (room < (INT64_C(3) << 30)) room = INT64_C(3) << 30;
if (room > lim / 2) room = lim / 2;
return lim - room;
}
static bool heap_anon_would_exceed(size_t bytes) {
int64_t wm = heap_anon_watermark();
if (wm <= 0) return false;
int64_t cur = atomic_load_explicit(&g_anon_committed, memory_order_relaxed);
return cur + (int64_t)bytes > wm;
}
#define RAY_FAR_VISIT_MS 100
static _Atomic(int64_t) g_far_visit_ms = INT64_MIN / 2;
int64_t ray_heap_anon_committed(void) {
return atomic_load_explicit(&g_anon_committed, memory_order_relaxed);
}
int64_t ray_heap_anon_peak(void) {
return atomic_load_explicit(&g_anon_peak, memory_order_relaxed);
}
int64_t ray_heap_anon_watermark(void) {
return heap_anon_watermark();
}
int64_t ray_heap_mem_budget(void) {
int64_t wm = atomic_load_explicit(&g_anon_watermark, memory_order_relaxed);
return wm > 0 ? wm : ray_sys_ram_limit();
}
void ray_heap_set_anon_watermark(int64_t bytes) {
atomic_store_explicit(&g_anon_watermark, bytes < 0 ? 0 : bytes,
memory_order_relaxed);
atomic_store_explicit(&g_far_visit_ms, INT64_MIN / 2, memory_order_relaxed);
}
ray_heap_t* ray_heap_registry[RAY_HEAP_REGISTRY_SIZE];
static atomic_flag ray_registry_lock_flag = ATOMIC_FLAG_INIT;
static void ray_registry_lock(void) { while (atomic_flag_test_and_set_explicit(&ray_registry_lock_flag, memory_order_acquire)) {} }
static void ray_registry_unlock(void) { atomic_flag_clear_explicit(&ray_registry_lock_flag, memory_order_release); }
_Atomic(ray_heap_t*) ray_heap_pending_merge = NULL;
static int heap_id_acquire(void) {
uint64_t start = atomic_fetch_add_explicit(&g_heap_id_cursor, 1,
memory_order_relaxed);
for (uint64_t off = 0; off < RAY_HEAP_ID_WORDS; off++) {
uint64_t idx = (start + off) % RAY_HEAP_ID_WORDS;
uint64_t word = atomic_load_explicit(&g_heap_id_bitmap[idx],
memory_order_relaxed);
while (~word != 0ULL) {
uint64_t free_bits = ~word;
uint64_t bit = (uint64_t)__builtin_ctzll(free_bits);
uint64_t mask = 1ULL << bit;
uint64_t new_word = word | mask;
if (atomic_compare_exchange_weak_explicit(
&g_heap_id_bitmap[idx], &word, new_word,
memory_order_acq_rel, memory_order_relaxed)) {
return (int)(idx * 64 + bit);
}
}
}
return -1;
}
static void heap_id_release(int id) {
if (id < 0 || id >= (int)RAY_HEAP_ID_BITS) return;
uint64_t idx = (uint64_t)id >> 6;
uint64_t bit = (uint64_t)id & 63ULL;
uint64_t mask = ~(1ULL << bit);
atomic_fetch_and_explicit(&g_heap_id_bitmap[idx], mask,
memory_order_release);
}
_Atomic(uint32_t) ray_parallel_flag = 0;
static uint8_t ceil_log2(size_t n) {
if (n <= 1) return 0;
return (uint8_t)(64 - __builtin_clzll(n - 1));
}
uint8_t ray_order_for_size(size_t data_size) {
if (data_size > SIZE_MAX - 32) return RAY_HEAP_MAX_ORDER + 1;
size_t total = data_size + 32;
uint8_t k = ceil_log2(total);
if (k < RAY_ORDER_MIN) k = RAY_ORDER_MIN;
return k;
}
static bool heap_add_pool(ray_heap_t* h, uint8_t order);
static bool heap_unpark(ray_heap_t* h, bool spill);
static void heap_unpark_all(ray_heap_t* h);
static bool heap_make_room(size_t bytes);
static int64_t heap_decay_threshold_ms(void);
static void heap_abandon_spill(ray_heap_t* h);
RAY_INLINE void heap_insert_block(ray_heap_t* h, ray_t* blk, uint8_t order) {
dfd_add(blk);
ray_fl_head_t* head = &h->freelist[order];
ray_t* first = head->fl_next;
blk->fl_prev = (ray_t*)head;
blk->fl_next = first;
first->fl_prev = blk;
head->fl_next = blk;
ray_atomic_store(&blk->rc, 0);
blk->order = order;
blk->attrs = 0;
h->avail |= (1ULL << order);
}
RAY_INLINE void heap_split_block(ray_heap_t* h, ray_t* blk,
uint8_t target_order, uint8_t block_order) {
while (block_order > target_order) {
block_order--;
ray_t* buddy = (ray_t*)((char*)blk + BSIZEOF(block_order));
buddy->mmod = 0;
buddy->order = block_order;
heap_insert_block(h, buddy, block_order);
}
}
static void heap_coalesce(ray_heap_t* h, ray_t* blk,
uintptr_t pool_base, uint8_t pool_order) {
dfd_remove(blk);
uint8_t order = blk->order;
if (atomic_load_explicit(&ray_parallel_flag, memory_order_relaxed) != 0) {
for (;; order++) {
if (order >= pool_order) break;
ray_t* buddy = ray_buddy_of(blk, order, pool_base);
if (ray_atomic_load(&buddy->rc) != 0 || buddy->order != order) break;
ray_pool_hdr_t* bphdr = ray_pool_of(buddy);
if (!bphdr || bphdr->heap_id != h->id) break;
fl_remove(buddy);
dfd_remove(buddy);
if (fl_empty(&h->freelist[order]))
h->avail &= ~(1ULL << order);
blk = (buddy < blk) ? buddy : blk;
}
heap_insert_block(h, blk, order);
return;
}
for (;; order++) {
if (order >= pool_order) break;
ray_t* buddy = ray_buddy_of(blk, order, pool_base);
__builtin_prefetch(buddy, 0, 1);
uint32_t buddy_rc = ray_atomic_load(&buddy->rc);
if (buddy_rc != 0 || buddy->order != order) break;
fl_remove(buddy);
if (fl_empty(&h->freelist[order]))
h->avail &= ~(1ULL << order);
blk = (buddy < blk) ? buddy : blk;
}
heap_insert_block(h, blk, order);
}
static bool heap_add_pool(ray_heap_t* h, uint8_t order) {
if (h->pool_count >= RAY_MAX_POOLS) return false;
uint8_t pool_order;
if (order >= RAY_HEAP_POOL_ORDER)
pool_order = order + 1;
else
pool_order = RAY_HEAP_POOL_ORDER;
if (pool_order > RAY_HEAP_MAX_ORDER) return false;
size_t pool_size = BSIZEOF(pool_order);
void* mem = NULL;
int swap_fd = -1;
char* swap_path = NULL;
bool over = heap_anon_would_exceed(pool_size);
if (!over)
mem = ray_vm_alloc_aligned(pool_size, pool_size);
#if !RAY_HEAP_FILE_SPILL
if (!mem && over) mem = ray_vm_alloc_aligned(pool_size, pool_size);
if (!mem) return false;
#else
if (!mem) do {
static _Atomic uint64_t swap_counter = 0;
uint64_t cnt = atomic_fetch_add_explicit(&swap_counter, 1, memory_order_relaxed);
size_t plen = strlen(h->swap_path);
size_t need = plen + 64;
swap_path = (char*)ray_sys_alloc(need);
if (!swap_path) break;
snprintf(swap_path, need, "%srayheap_%d_%u_%llu.dat",
h->swap_path, (int)getpid(), (unsigned)h->id,
(unsigned long long)cnt);
swap_fd = open(swap_path, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
if (swap_fd < 0) {
ray_sys_free(swap_path);
swap_path = NULL;
break;
}
if (heap_preallocate(swap_fd, 0, (off_t)pool_size) != 0) {
close(swap_fd);
unlink(swap_path);
ray_sys_free(swap_path);
swap_fd = -1;
swap_path = NULL;
break;
}
size_t reserve_size = pool_size + pool_size;
void* anon = mmap(NULL, reserve_size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (anon == MAP_FAILED) {
close(swap_fd);
unlink(swap_path);
ray_sys_free(swap_path);
swap_fd = -1;
swap_path = NULL;
break;
}
uintptr_t addr = (uintptr_t)anon;
uintptr_t aligned = (addr + pool_size - 1) & ~(pool_size - 1);
if (aligned > addr)
munmap(anon, aligned - addr);
uintptr_t end = addr + reserve_size;
uintptr_t aligned_end = aligned + pool_size;
if (end > aligned_end)
munmap((void*)aligned_end, end - aligned_end);
void* mapped = mmap((void*)aligned, pool_size,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_FIXED, swap_fd, 0);
if (mapped == MAP_FAILED) {
munmap((void*)aligned, pool_size);
close(swap_fd);
unlink(swap_path);
ray_sys_free(swap_path);
swap_fd = -1;
swap_path = NULL;
break;
}
ray_sys_track_add((int64_t)pool_size);
mem = (void*)aligned;
unlink(swap_path);
ray_sys_free(swap_path);
swap_path = NULL;
} while (0);
if (!mem && over) mem = ray_vm_alloc_aligned(pool_size, pool_size);
if (!mem) return false;
#endif
uint8_t pool_hugepage = 0;
if (swap_fd < 0) {
const char* hp = getenv("RAY_HEAP_HUGEPAGE");
if (!(hp && hp[0] == '0'))
pool_hugepage = ray_vm_hugepage(mem, pool_size) ? 1 : 0;
}
ray_t* hdr_block = (ray_t*)mem;
memset(hdr_block, 0, BSIZEOF(RAY_ORDER_MIN));
hdr_block->mmod = 0;
hdr_block->order = RAY_ORDER_MIN;
ray_atomic_store(&hdr_block->rc, 1);
ray_pool_hdr_t* hdr = (ray_pool_hdr_t*)hdr_block;
hdr->heap_id = h->id;
hdr->pool_order = pool_order;
hdr->vm_base = mem;
for (uint8_t o = pool_order; o > RAY_ORDER_MIN; o--) {
ray_t* right = (ray_t*)((char*)mem + BSIZEOF(o - 1));
right->mmod = 0;
right->order = (uint8_t)(o - 1);
heap_insert_block(h, right, (uint8_t)(o - 1));
}
h->pools[h->pool_count] = (ray_pool_entry_t){
.base = mem,
.pool_order = pool_order,
.backed = (swap_fd >= 0) ? 1 : 0,
.hugepage = pool_hugepage,
.swap_fd = swap_fd,
.swap_path = swap_path,
};
h->pool_count++;
if (swap_fd < 0)
heap_anon_commit((int64_t)pool_size, true, false);
else
atomic_fetch_add_explicit(&g_spill_pools, 1, memory_order_relaxed);
if (over || swap_fd >= 0) {
heap_anon_spilled();
h->spilled = 1;
}
return true;
}
static void heap_flush_slabs(ray_heap_t* h) {
for (int i = 0; i < RAY_SLAB_ORDERS; i++) {
while (h->slabs[i].count > 0) {
ray_t* blk = h->slabs[i].stack[--h->slabs[i].count];
dfd_remove(blk);
int pidx = heap_find_pool(h, blk);
uintptr_t pb;
uint8_t po;
if (pidx >= 0) {
pb = (uintptr_t)h->pools[pidx].base;
po = h->pools[pidx].pool_order;
} else {
ray_pool_hdr_t* phdr = ray_pool_of(blk);
if (!phdr) continue;
pb = (uintptr_t)phdr;
po = phdr->pool_order;
}
heap_coalesce(h, blk, pb, po);
}
}
}
static void heap_drain_foreign(ray_heap_t* h) {
ray_t* blk = atomic_exchange_explicit(&h->foreign, NULL,
memory_order_acquire);
while (blk) {
ray_t* next = blk->fl_next;
int pidx = heap_find_pool(h, blk);
uintptr_t pb;
uint8_t po;
if (pidx >= 0) {
pb = (uintptr_t)h->pools[pidx].base;
po = h->pools[pidx].pool_order;
} else {
ray_pool_hdr_t* phdr = ray_pool_of(blk);
if (!phdr) { blk = next; continue; }
pb = (uintptr_t)phdr;
po = phdr->pool_order;
if (phdr->heap_id != h->id) {
heap_coalesce(h, blk, pb, po);
blk = next;
continue;
}
}
RAY_STAT(h->stats.bytes_allocated -= BSIZEOF(blk->order));
heap_coalesce(h, blk, pb, po);
blk = next;
}
}
static bool ray_atom_str_is_sso(const ray_t* s) {
if (s->slen >= 1 && s->slen <= 7) return true;
if (s->slen == 0 && s->obj == NULL) return true;
return false;
}
static bool ray_atom_owns_obj(const ray_t* v) {
if (v->type == -RAY_GUID) return v->obj != NULL;
if (v->type == -RAY_STR) return !ray_atom_str_is_sso(v);
return false;
}
static void ray_release_owned_refs(ray_t* v) {
if (!v || RAY_IS_ERR(v)) return;
if (ray_is_atom(v)) {
if (v->type == RAY_LAMBDA) {
ray_t** slots = (ray_t**)ray_data(v);
for (int i = 0; i < 4; i++) {
if (slots[i] && !RAY_IS_ERR(slots[i]))
ray_release(slots[i]);
}
if (LAMBDA_NFO(v)) ray_release(LAMBDA_NFO(v));
if (LAMBDA_DBG(v)) ray_release(LAMBDA_DBG(v));
if (LAMBDA_CLOSURE(v)) ray_release(LAMBDA_CLOSURE(v));
if (LAMBDA_CAPROWS(v)) ray_release(LAMBDA_CAPROWS(v));
return;
}
if (v->type == RAY_LAZY) {
ray_graph_t* g = RAY_LAZY_GRAPH(v);
if (g) {
ray_graph_free(g);
RAY_LAZY_GRAPH(v) = NULL;
}
return;
}
if (v->type == -RAY_I64 && (v->attrs & RAY_ATTR_HNSW)) {
ray_hnsw_t* idx = (ray_hnsw_t*)(uintptr_t)v->i64;
if (idx) ray_hnsw_free(idx);
v->i64 = 0;
v->attrs &= (uint8_t)~RAY_ATTR_HNSW;
return;
}
if (v->type == -RAY_I64 && (v->attrs & RAY_ATTR_GRAPH)) {
ray_rel_t* rel = (ray_rel_t*)(uintptr_t)v->i64;
if (rel) ray_rel_free(rel);
v->i64 = 0;
v->attrs &= (uint8_t)~RAY_ATTR_GRAPH;
return;
}
if (ray_atom_owns_obj(v) && v->obj && !RAY_IS_ERR(v->obj))
ray_release(v->obj);
return;
}
if (v->attrs & RAY_ATTR_SLICE) {
if (v->slice_parent && !RAY_IS_ERR(v->slice_parent))
ray_release(v->slice_parent);
return;
}
if (v->type == RAY_INDEX) {
ray_index_t* ix = ray_index_payload(v);
ray_index_release_payload(ix);
ray_index_release_saved(ix);
return;
}
if (v->attrs & RAY_ATTR_HAS_INDEX) {
if (v->index && !RAY_IS_ERR(v->index) && v->index->mmod != 1)
ray_release(v->index);
}
if (v->type == RAY_STR && v->str_pool && !RAY_IS_ERR(v->str_pool)) {
ray_release(v->str_pool);
return;
}
if (v->type == RAY_SYM && v->sym_domain) {
ray_sym_domain_release(v->sym_domain);
return;
}
if (RAY_IS_PARTED(v->type)) {
int64_t n_segs = v->len;
ray_t** segs = (ray_t**)ray_data(v);
for (int64_t i = 0; i < n_segs; i++) {
if (segs[i] && !RAY_IS_ERR(segs[i]))
ray_release(segs[i]);
}
return;
}
if (v->type == RAY_MAPCOMMON) {
ray_t** ptrs = (ray_t**)ray_data(v);
if (ptrs[0] && !RAY_IS_ERR(ptrs[0])) ray_release(ptrs[0]);
if (ptrs[1] && !RAY_IS_ERR(ptrs[1])) ray_release(ptrs[1]);
return;
}
if (v->type == RAY_TABLE || v->type == RAY_DICT) {
ray_t** slots = (ray_t**)ray_data(v);
if (slots[0] && !RAY_IS_ERR(slots[0])) ray_release(slots[0]);
if (slots[1] && !RAY_IS_ERR(slots[1])) ray_release(slots[1]);
return;
}
if (v->type == RAY_LIST) {
ray_t** ptrs = (ray_t**)ray_data(v);
for (int64_t i = 0; i < v->len; i++) {
ray_t* child = ptrs[i];
if (child && !RAY_IS_ERR(child)) ray_release(child);
}
}
}
bool ray_retain_owned_refs(ray_t* v) {
if (!v || RAY_IS_ERR(v)) return true;
if (ray_is_atom(v)) {
if (v->type == RAY_LAMBDA) {
ray_t** slots = (ray_t**)ray_data(v);
for (int i = 0; i < 4; i++) {
if (slots[i] && !RAY_IS_ERR(slots[i]))
ray_retain(slots[i]);
}
if (LAMBDA_NFO(v)) ray_retain(LAMBDA_NFO(v));
if (LAMBDA_DBG(v)) ray_retain(LAMBDA_DBG(v));
if (LAMBDA_CLOSURE(v)) ray_retain(LAMBDA_CLOSURE(v));
if (LAMBDA_CAPROWS(v)) ray_retain(LAMBDA_CAPROWS(v));
return true;
}
if (v->type == RAY_LAZY) return true;
if (v->type == -RAY_I64 && (v->attrs & RAY_ATTR_HNSW)) {
ray_hnsw_t* src = (ray_hnsw_t*)(uintptr_t)v->i64;
if (src) {
ray_hnsw_t* dup = ray_hnsw_clone(src);
if (!dup) {
v->i64 = 0;
v->attrs &= (uint8_t)~RAY_ATTR_HNSW;
return false;
}
v->i64 = (int64_t)(uintptr_t)dup;
}
return true;
}
if (v->type == -RAY_I64 && (v->attrs & RAY_ATTR_GRAPH)) {
v->i64 = 0;
v->attrs &= (uint8_t)~RAY_ATTR_GRAPH;
return true;
}
if (ray_atom_owns_obj(v) && v->obj && !RAY_IS_ERR(v->obj))
ray_retain(v->obj);
return true;
}
if (v->attrs & RAY_ATTR_SLICE) {
if (v->slice_parent && !RAY_IS_ERR(v->slice_parent))
ray_retain(v->slice_parent);
return true;
}
if (v->type == RAY_INDEX) {
ray_index_t* ix = ray_index_payload(v);
ray_index_retain_payload(ix);
ray_index_retain_saved(ix);
return true;
}
if (v->attrs & RAY_ATTR_HAS_INDEX) {
if (v->index && !RAY_IS_ERR(v->index) && v->index->mmod != 1)
ray_retain(v->index);
}
if (v->type == RAY_STR && v->str_pool && !RAY_IS_ERR(v->str_pool))
ray_retain(v->str_pool);
if (v->type == RAY_SYM && v->sym_domain) {
ray_sym_domain_retain(v->sym_domain);
return true;
}
if (RAY_IS_PARTED(v->type)) {
int64_t n_segs = v->len;
ray_t** segs = (ray_t**)ray_data(v);
for (int64_t i = 0; i < n_segs; i++) {
if (segs[i] && !RAY_IS_ERR(segs[i]))
ray_retain(segs[i]);
}
return true;
}
if (v->type == RAY_MAPCOMMON) {
ray_t** ptrs = (ray_t**)ray_data(v);
if (ptrs[0] && !RAY_IS_ERR(ptrs[0])) ray_retain(ptrs[0]);
if (ptrs[1] && !RAY_IS_ERR(ptrs[1])) ray_retain(ptrs[1]);
return true;
}
if (v->type == RAY_TABLE || v->type == RAY_DICT) {
ray_t** slots = (ray_t**)ray_data(v);
if (slots[0] && !RAY_IS_ERR(slots[0])) ray_retain(slots[0]);
if (slots[1] && !RAY_IS_ERR(slots[1])) ray_retain(slots[1]);
return true;
}
if (v->type == RAY_LIST) {
ray_t** ptrs = (ray_t**)ray_data(v);
for (int64_t i = 0; i < v->len; i++) {
ray_t* child = ptrs[i];
if (child && !RAY_IS_ERR(child)) ray_retain(child);
}
}
return true;
}
static void ray_detach_owned_refs(ray_t* v) {
if (!v || RAY_IS_ERR(v)) return;
if (ray_is_atom(v)) {
if (v->type == RAY_LAMBDA) {
ray_t** slots = (ray_t**)ray_data(v);
for (int i = 0; i < 4; i++) slots[i] = NULL;
LAMBDA_NFO(v) = NULL;
LAMBDA_DBG(v) = NULL;
LAMBDA_CLOSURE(v) = NULL;
LAMBDA_CAPROWS(v) = NULL;
LAMBDA_CAPQ(v) = 0;
return;
}
if (v->type == RAY_LAZY) {
RAY_LAZY_GRAPH(v) = NULL;
RAY_LAZY_OP(v) = NULL;
return;
}
if (v->type == -RAY_I64 && (v->attrs & RAY_ATTR_HNSW)) {
v->i64 = 0;
v->attrs &= (uint8_t)~RAY_ATTR_HNSW;
return;
}
if (v->type == -RAY_I64 && (v->attrs & RAY_ATTR_GRAPH)) {
v->i64 = 0;
v->attrs &= (uint8_t)~RAY_ATTR_GRAPH;
return;
}
if (ray_atom_owns_obj(v)) v->obj = NULL;
return;
}
if (v->attrs & RAY_ATTR_SLICE) {
v->slice_parent = NULL;
v->slice_offset = 0;
v->attrs &= (uint8_t)~RAY_ATTR_SLICE;
return;
}
if (v->type == RAY_INDEX) {
ray_index_t* ix = ray_index_payload(v);
switch ((ray_idx_kind_t)ix->kind) {
case RAY_IDX_HASH:
ix->u.hash.table = ix->u.hash.gkeys = NULL;
ix->u.hash.offs = ix->u.hash.rows = NULL;
break;
case RAY_IDX_SORT: ix->u.sort.perm = NULL; break;
case RAY_IDX_BLOOM: ix->u.bloom.bits = NULL; break;
default: break;
}
memset(ix->saved_aux, 0, 16);
ix->saved_attrs = 0;
return;
}
if (v->attrs & RAY_ATTR_HAS_INDEX) {
v->index = NULL;
v->_idx_pad = NULL;
v->attrs &= (uint8_t)~RAY_ATTR_HAS_INDEX;
return;
}
if (v->type == RAY_STR) {
v->str_pool = NULL;
}
if (v->type == RAY_SYM) {
v->sym_domain = NULL;
return;
}
if (RAY_IS_PARTED(v->type)) {
int64_t n_segs = v->len;
ray_t** segs = (ray_t**)ray_data(v);
for (int64_t i = 0; i < n_segs; i++)
segs[i] = NULL;
return;
}
if (v->type == RAY_MAPCOMMON) {
ray_t** ptrs = (ray_t**)ray_data(v);
ptrs[0] = NULL;
ptrs[1] = NULL;
return;
}
if (v->type == RAY_TABLE || v->type == RAY_DICT) {
ray_t** slots = (ray_t**)ray_data(v);
slots[0] = NULL;
slots[1] = NULL;
v->len = 0;
return;
}
if (v->type == RAY_LIST) {
v->len = 0;
}
}
static void* heap_direct_map_file(ray_heap_t* h, size_t map_size,
int* out_fd, char** out_path) {
#if !RAY_HEAP_FILE_SPILL
(void)h; (void)map_size; (void)out_fd; (void)out_path;
return NULL;
#else
static _Atomic uint64_t direct_swap_counter = 0;
uint64_t cnt = atomic_fetch_add_explicit(&direct_swap_counter, 1,
memory_order_relaxed);
size_t plen = strlen(h->swap_path);
size_t need = plen + 64;
char* path = (char*)ray_sys_alloc(need);
if (!path) return NULL;
snprintf(path, need, "%sraydirect_%d_%u_%llu.dat", h->swap_path,
(int)getpid(), (unsigned)h->id, (unsigned long long)cnt);
int fd = open(path, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
if (fd < 0) { ray_sys_free(path); return NULL; }
if (heap_preallocate(fd, 0, (off_t)map_size) != 0) {
close(fd); unlink(path); ray_sys_free(path); return NULL;
}
void* mapped = mmap(NULL, map_size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (mapped == MAP_FAILED) {
close(fd); unlink(path); ray_sys_free(path); return NULL;
}
ray_sys_track_add((int64_t)map_size);
unlink(path);
ray_sys_free(path);
*out_fd = fd;
*out_path = NULL;
return mapped;
#endif
}
typedef struct ray_direct_cache_node {
struct ray_direct_cache_node* next;
struct ray_direct_cache_node* prev;
size_t map_size;
} ray_direct_cache_node_t;
_Static_assert(sizeof(ray_direct_cache_node_t) <= RAY_DIRECT_HDR,
"cached links must fit in the dead direct-allocation prefix");
static ray_direct_cache_node_t* g_direct_cache_head;
static ray_direct_cache_node_t* g_direct_cache_tail;
static size_t g_direct_cache_bytes;
static _Atomic(int) g_direct_cache_spin = 0;
static inline void direct_cache_lock(void) {
while (atomic_exchange_explicit(&g_direct_cache_spin, 1,
memory_order_acquire)) { }
}
static inline void direct_cache_unlock(void) {
atomic_store_explicit(&g_direct_cache_spin, 0, memory_order_release);
}
static size_t direct_cache_budget(void) {
int64_t wm = ray_heap_mem_budget();
size_t b = (wm > 0) ? (size_t)wm / 16 : 0;
return b;
}
static void direct_cache_unlink(ray_direct_cache_node_t* node) {
if (node->prev) node->prev->next = node->next;
else g_direct_cache_head = node->next;
if (node->next) node->next->prev = node->prev;
else g_direct_cache_tail = node->prev;
g_direct_cache_bytes -= node->map_size;
}
static void direct_cache_release(ray_direct_cache_node_t* node) {
while (node) {
ray_direct_cache_node_t* next = node->next;
size_t bytes = node->map_size;
atomic_fetch_sub_explicit(&g_anon_committed, (int64_t)bytes, memory_order_relaxed);
ray_vm_free(node, bytes);
node = next;
}
}
static void* direct_cache_take(size_t need, size_t* out_size) {
direct_cache_lock();
size_t slack = need / 4;
if (slack < (2u << 20)) slack = (2u << 20);
ray_direct_cache_node_t* best = NULL;
for (ray_direct_cache_node_t* node = g_direct_cache_head; node; node = node->next) {
size_t bytes = node->map_size;
if (bytes >= need && bytes - need <= slack && (!best || bytes < best->map_size)) {
best = node;
if (bytes == need) break;
}
}
if (best) {
*out_size = best->map_size;
direct_cache_unlink(best);
}
direct_cache_unlock();
return best;
}
static bool direct_cache_put(void* base, size_t map_size) {
size_t budget = direct_cache_budget();
if (map_size > budget) return false;
ray_direct_cache_node_t* evicted = NULL;
direct_cache_lock();
while (g_direct_cache_bytes > budget - map_size) {
ray_direct_cache_node_t* old = g_direct_cache_head;
direct_cache_unlink(old);
old->next = evicted;
evicted = old;
}
ray_direct_cache_node_t* node = base;
node->map_size = map_size;
node->next = NULL;
node->prev = g_direct_cache_tail;
if (g_direct_cache_tail) g_direct_cache_tail->next = node;
else g_direct_cache_head = node;
g_direct_cache_tail = node;
g_direct_cache_bytes += map_size;
direct_cache_unlock();
direct_cache_release(evicted);
return true;
}
static void direct_cache_drain(void) {
direct_cache_lock();
ray_direct_cache_node_t* nodes = g_direct_cache_head;
g_direct_cache_head = g_direct_cache_tail = NULL;
g_direct_cache_bytes = 0;
direct_cache_unlock();
direct_cache_release(nodes);
}
void ray_heap_direct_cache_drain(void) {
direct_cache_drain();
}
static int64_t direct_cache_bytes(void) {
direct_cache_lock();
size_t b = g_direct_cache_bytes;
direct_cache_unlock();
return (int64_t)b;
}
static ray_t* heap_alloc_direct(ray_heap_t* h, size_t data_size) {
if (data_size > SIZE_MAX - RAY_DIRECT_HDR - 32 - 4095) return NULL;
size_t map_size = (RAY_DIRECT_HDR + 32 + data_size + 4095) & ~(size_t)4095;
void* base = NULL;
int swap_fd = -1;
char* swap_path = NULL;
size_t cached_size = 0;
base = direct_cache_take(map_size, &cached_size);
if (base) {
map_size = cached_size;
} else {
bool force_file = heap_anon_would_exceed(map_size) && heap_make_room(map_size);
if (!force_file)
base = ray_vm_alloc(map_size);
if (!base) {
base = heap_direct_map_file(h, map_size, &swap_fd, &swap_path);
if (!base && force_file) base = ray_vm_alloc(map_size);
if (!base) return NULL;
}
if (swap_fd < 0)
heap_anon_commit((int64_t)map_size, true, true);
if (force_file || swap_fd >= 0)
heap_anon_spilled();
}
ray_direct_hdr_t* hdr = (ray_direct_hdr_t*)base;
hdr->map_size = map_size;
hdr->swap_fd = swap_fd;
hdr->swap_path = swap_path;
ray_t* v = (ray_t*)((char*)base + RAY_DIRECT_HDR);
memset(v, 0, 32);
v->mmod = 0;
v->order = RAY_ORDER_DIRECT;
if (RAY_UNLIKELY(ray_rc_sync))
ray_atomic_store(&v->rc, 1);
else
v->rc = 1;
atomic_fetch_add_explicit(&g_direct_bytes, (int64_t)map_size, memory_order_relaxed);
atomic_fetch_add_explicit(&g_direct_count, 1, memory_order_relaxed);
RAY_STAT(h->stats.alloc_count++);
return v;
}
ray_t* ray_alloc(size_t data_size) {
ray_heap_t* h = ray_tl_heap;
if (RAY_UNLIKELY(!h)) {
ray_heap_init();
h = ray_tl_heap;
if (!h) return NULL;
}
uint8_t order = ray_order_for_size(data_size);
if (RAY_UNLIKELY(order >= RAY_HEAP_POOL_ORDER)) {
ray_t* v = heap_alloc_direct(h, data_size);
if (v) {
ray_direct_hdr_t* hdr = (ray_direct_hdr_t*)((char*)v - RAY_DIRECT_HDR);
mem_trace_note_alloc(hdr->map_size);
return v;
}
if (order > RAY_HEAP_MAX_ORDER) return NULL;
}
if (order > RAY_HEAP_MAX_ORDER) return NULL;
if (RAY_LIKELY(IS_SLAB_ORDER(order))) {
int idx = SLAB_INDEX(order);
if (RAY_LIKELY(h->slabs[idx].count > 0)) {
ray_t* v = h->slabs[idx].stack[--h->slabs[idx].count];
dfd_remove(v);
memset(v, 0, 32);
v->order = order;
if (RAY_UNLIKELY(ray_rc_sync))
ray_atomic_store(&v->rc, 1);
else
v->rc = 1;
RAY_STAT(h->stats.alloc_count++);
RAY_STAT(h->stats.slab_hits++);
RAY_STAT(h->stats.bytes_allocated += BSIZEOF(order));
RAY_STAT(h->stats.peak_bytes = h->stats.bytes_allocated > h->stats.peak_bytes
? h->stats.bytes_allocated : h->stats.peak_bytes);
mem_trace_note_alloc(BSIZEOF(order));
return v;
}
}
uint64_t candidates = h->avail & (UINT64_MAX << order);
uint8_t found_order;
for (;;) {
if (RAY_UNLIKELY(candidates == 0)) {
heap_drain_foreign(h);
candidates = h->avail & (UINT64_MAX << order);
if (candidates == 0 && h->parked
&& (heap_unpark(h, false) || heap_unpark(h, true)))
candidates = h->avail & (UINT64_MAX << order);
if (candidates == 0) {
if (!heap_add_pool(h, order)) return NULL;
candidates = h->avail & (UINT64_MAX << order);
if (candidates == 0) return NULL;
}
}
found_order = (uint8_t)__builtin_ctzll(candidates);
if (!fl_empty(&h->freelist[found_order])) break;
h->avail &= ~(1ULL << found_order);
candidates &= ~(1ULL << found_order);
}
ray_fl_head_t* head = &h->freelist[found_order];
ray_t* blk = head->fl_next;
fl_remove(blk);
dfd_remove(blk);
if (fl_empty(head))
h->avail &= ~(1ULL << found_order);
heap_split_block(h, blk, order, found_order);
memset(blk, 0, 32);
blk->mmod = 0;
blk->order = order;
if (RAY_UNLIKELY(ray_rc_sync))
ray_atomic_store(&blk->rc, 1);
else
blk->rc = 1;
RAY_STAT(h->stats.alloc_count++);
RAY_STAT(h->stats.bytes_allocated += BSIZEOF(order));
RAY_STAT(h->stats.peak_bytes = h->stats.bytes_allocated > h->stats.peak_bytes
? h->stats.bytes_allocated : h->stats.peak_bytes);
mem_trace_note_alloc(BSIZEOF(order));
return blk;
}
#define RAY_FMAP_BUCKETS 256
static ray_file_map_t* g_fmap[RAY_FMAP_BUCKETS];
static _Atomic(int) g_fmap_lock = 0;
static inline void fmap_lock(void) {
while (atomic_exchange_explicit(&g_fmap_lock, 1, memory_order_acquire))
RAY_CPU_RELAX();
}
static inline void fmap_unlock(void) {
atomic_store_explicit(&g_fmap_lock, 0, memory_order_release);
}
static inline size_t fmap_bucket(const void* base) {
return ((uintptr_t)base >> 12) & (RAY_FMAP_BUCKETS - 1);
}
void ray_file_map_register(const void* base, ray_file_map_t* m) {
if (!m) return;
size_t b = fmap_bucket(base);
fmap_lock();
m->next = g_fmap[b];
g_fmap[b] = m;
fmap_unlock();
}
ray_file_map_t* ray_file_map_lookup(const void* base) {
size_t b = fmap_bucket(base);
fmap_lock();
ray_file_map_t* m = g_fmap[b];
while (m && m->base != base) m = m->next;
fmap_unlock();
return m;
}
void ray_file_map_release(ray_file_map_t* m) {
if (!m) return;
if (ray_atomic_dec(&m->rc) > 1) return;
size_t b = fmap_bucket(m->base);
fmap_lock();
ray_file_map_t** pp = &g_fmap[b];
while (*pp && *pp != m) pp = &(*pp)->next;
if (*pp) *pp = m->next;
fmap_unlock();
ray_vm_unmap_file(m->base, m->len);
ray_sys_free(m);
}
void ray_free(ray_t* v) {
if (!v || RAY_IS_ERR(v)) return;
if (v->attrs & RAY_ATTR_ARENA) return;
#ifdef DEBUG
if (dfd_contains(v)) dfd_report("ray_free (double free)", v);
#endif
ray_atomic_store(&v->rc, 1);
ray_file_map_t* owned_map = (v->mmod == 3) ? v->file_map : NULL;
ray_file_map_t* col_map = NULL;
if (v->mmod == 1 && v->type == RAY_STR &&
v->str_pool && !RAY_IS_ERR(v->str_pool) && v->str_pool->mmod == 3)
col_map = v->str_pool->file_map;
ray_release_owned_refs(v);
ray_heap_t* h = ray_tl_heap;
if (owned_map) {
ray_file_map_release(owned_map);
if (h) RAY_STAT(h->stats.free_count++);
return;
}
if (v->mmod == 1) {
if (v->type == RAY_TABLE || v->type == RAY_DICT || v->type == RAY_LIST) return;
ray_file_map_t* m = col_map;
if (!m) m = ray_file_map_lookup(v);
if (m) {
ray_file_map_release(m);
if (h) RAY_STAT(h->stats.free_count++);
return;
}
if (v->type > 0 && v->type < RAY_TYPE_COUNT) {
uint8_t esz = ray_sym_elem_size(v->type, v->attrs);
size_t data_size = 32 + (size_t)v->len * esz;
if (v->type == RAY_STR) {
size_t pool_len = 0;
if (v->str_pool && !RAY_IS_ERR(v->str_pool) && v->str_pool->len > 0)
pool_len = (size_t)v->str_pool->len;
data_size += 32 + pool_len;
}
if ((v->attrs & RAY_ATTR_HAS_INDEX) && v->index && !RAY_IS_ERR(v->index)) {
ray_index_t* ix = ray_index_payload(v->index);
if (ix->markers & RAY_MARK_MMAP) {
int64_t region_off = ((int64_t)data_size + 31) & ~(int64_t)31;
data_size = (size_t)(region_off + ray_index_inline_size(ix));
}
}
size_t mapped_size = (data_size + 4095) & ~(size_t)4095;
ray_vm_unmap_file(v, mapped_size);
} else {
ray_vm_unmap_file(v, 4096);
}
if (h) RAY_STAT(h->stats.free_count++);
return;
}
if (v->mmod == 2) return;
if (RAY_UNLIKELY(v->order > RAY_HEAP_MAX_ORDER)) {
char* base = (char*)v - RAY_DIRECT_HDR;
ray_direct_hdr_t* hdr = (ray_direct_hdr_t*)base;
size_t map_size = hdr->map_size;
int swap_fd = hdr->swap_fd;
char* swap_path = hdr->swap_path;
mem_trace_note_free(map_size);
if (h) RAY_STAT(h->stats.free_count++);
atomic_fetch_sub_explicit(&g_direct_bytes, (int64_t)map_size, memory_order_relaxed);
atomic_fetch_sub_explicit(&g_direct_count, 1, memory_order_relaxed);
#if RAY_HEAP_FILE_SPILL
if (swap_fd >= 0) {
munmap(base, map_size);
ray_sys_track_sub((int64_t)map_size);
close(swap_fd);
if (swap_path) { unlink(swap_path); ray_sys_free(swap_path); }
} else
#else
(void)swap_fd; (void)swap_path;
#endif
if (direct_cache_put(base, map_size)) {
} else {
atomic_fetch_sub_explicit(&g_anon_committed, (int64_t)map_size,
memory_order_relaxed);
ray_vm_free(base, map_size);
}
return;
}
if (!h) return;
uint8_t order = v->order;
if (order < RAY_ORDER_MIN || order > RAY_HEAP_MAX_ORDER) return;
mem_trace_note_free(BSIZEOF(order));
#if RAY_MEM_STATS
size_t block_size = BSIZEOF(order);
#endif
int pidx = -1;
if (h->last_pool_idx < h->pool_count) {
uintptr_t pb = (uintptr_t)h->pools[h->last_pool_idx].base;
uintptr_t pe = pb + BSIZEOF(h->pools[h->last_pool_idx].pool_order);
if ((uintptr_t)v >= pb && (uintptr_t)v < pe)
pidx = (int)h->last_pool_idx;
}
if (pidx < 0) pidx = heap_find_pool(h, v);
if (pidx >= 0) {
h->last_pool_idx = (uint32_t)pidx;
uintptr_t pool_base = (uintptr_t)h->pools[pidx].base;
uint8_t pool_order = h->pools[pidx].pool_order;
if (IS_SLAB_ORDER(order)) {
int idx = SLAB_INDEX(order);
if (h->slabs[idx].count < h->slab_cap[idx]) {
ray_atomic_store(&v->rc, 1);
dfd_add(v);
h->slabs[idx].stack[h->slabs[idx].count++] = v;
RAY_STAT(h->stats.free_count++);
RAY_STAT(h->stats.bytes_allocated -= block_size);
return;
}
}
heap_coalesce(h, v, pool_base, pool_order);
RAY_STAT(h->stats.free_count++);
RAY_STAT(h->stats.bytes_allocated -= block_size);
return;
}
ray_pool_hdr_t* phdr = ray_pool_of(v);
if (RAY_UNLIKELY(!phdr)) return;
uint16_t owner_id = phdr->heap_id;
ray_heap_t* owner = ray_heap_registry[owner_id % RAY_HEAP_REGISTRY_SIZE];
if (RAY_UNLIKELY(!owner || owner->id != owner_id)) owner = h;
dfd_add(v);
ray_t* head = atomic_load_explicit(&owner->foreign, memory_order_relaxed);
do {
v->fl_next = head;
} while (!atomic_compare_exchange_weak_explicit(
&owner->foreign, &head, v,
memory_order_release, memory_order_relaxed));
RAY_STAT(h->stats.free_count++);
}
void* ray_alloc_raw(size_t n) {
ray_t* v = ray_alloc(n);
if (!v || RAY_IS_ERR(v)) return NULL;
v->type = RAY_U8;
v->len = (int64_t)n;
return ray_data(v);
}
void* ray_calloc_raw(size_t n) {
void* p = ray_alloc_raw(n);
if (p && n) memset(p, 0, n);
return p;
}
void ray_free_raw(void* p) {
if (!p) return;
ray_free((ray_t*)((char*)p - 32));
}
bool ray_raw_discard(void* p, size_t from, size_t upto) {
#if RAY_HEAP_FILE_SPILL && defined(MADV_REMOVE)
if (!p || upto <= from) return false;
ray_t* v = (ray_t*)((char*)p - 32);
if (!ray_is_direct(v) || !ray_direct_file_backed(v)) return false;
size_t room = ray_block_data_bytes(v);
if (upto > room) upto = room;
long ps = sysconf(_SC_PAGESIZE);
uintptr_t pg = ps > 0 ? (uintptr_t)ps : 4096;
uintptr_t first = ((uintptr_t)p + pg - 1) & ~(pg - 1);
uintptr_t lo = ((uintptr_t)p + from) & ~(pg - 1);
uintptr_t hi = ((uintptr_t)p + upto) & ~(pg - 1);
if (lo < first) lo = first;
if (hi <= lo) return false;
return madvise((void*)lo, hi - lo, MADV_REMOVE) == 0;
#else
(void)p; (void)from; (void)upto;
return false;
#endif
}
void* ray_realloc_raw(void* p, size_t n) {
if (!p) return ray_alloc_raw(n);
ray_t* v = (ray_t*)((char*)p - 32);
size_t cur = ray_block_data_bytes(v);
if (n <= cur) { v->len = (int64_t)n; return p; }
void* np = ray_alloc_raw(n);
if (!np) return NULL;
memcpy(np, p, cur);
ray_free_raw(p);
return np;
}
ray_t* ray_alloc_copy(ray_t* v) {
if (!v || RAY_IS_ERR(v)) return NULL;
size_t data_size;
if (v->attrs & RAY_ATTR_SLICE) {
data_size = 0;
} else if (ray_is_atom(v)) {
data_size = 0;
} else if (v->type == RAY_TABLE || v->type == RAY_DICT) {
data_size = 2 * sizeof(ray_t*);
} else if (RAY_IS_PARTED(v->type) || v->type == RAY_MAPCOMMON) {
int64_t n_ptrs = v->len;
if (v->type == RAY_MAPCOMMON) n_ptrs = 2;
if (n_ptrs < 0) return ray_error("oom", NULL);
data_size = (size_t)n_ptrs * sizeof(ray_t*);
} else if (v->type == RAY_LIST) {
if (v->len < 0 || (uint64_t)v->len > SIZE_MAX / sizeof(ray_t*))
return ray_error("oom", NULL);
data_size = (size_t)ray_len(v) * sizeof(ray_t*);
} else {
int8_t t = ray_type(v);
if (t <= 0 || t >= RAY_TYPE_COUNT)
data_size = 0;
else {
uint8_t esz = ray_sym_elem_size(t, v->attrs);
if (v->len < 0 || (esz > 0 && (uint64_t)v->len > SIZE_MAX / esz))
return ray_error("oom", NULL);
data_size = (size_t)ray_len(v) * esz;
}
}
ray_t* copy = ray_alloc(data_size);
if (!copy) return NULL;
uint8_t new_order = copy->order;
uint8_t new_mmod = copy->mmod;
memcpy(copy, v, 32 + data_size);
copy->mmod = new_mmod;
copy->order = new_order;
if (RAY_UNLIKELY(ray_rc_sync))
ray_atomic_store(©->rc, 1);
else
copy->rc = 1;
if (copy->type == RAY_TABLE && (copy->attrs & RAY_ATTR_HAS_INDEX)) {
copy->index = NULL;
copy->_idx_pad = NULL;
copy->attrs &= (uint8_t)~RAY_ATTR_HAS_INDEX;
}
if (!ray_retain_owned_refs(copy)) {
ray_free(copy);
return ray_error("oom", NULL);
}
return copy;
}
ray_t* ray_scratch_alloc(size_t data_size) {
return ray_alloc(data_size);
}
ray_t* ray_scratch_realloc(ray_t* v, size_t new_data_size) {
ray_t* new_v = ray_alloc(new_data_size);
if (!new_v) return NULL;
if (v && !RAY_IS_ERR(v)) {
size_t old_data;
if (v->attrs & RAY_ATTR_SLICE)
old_data = 0;
else if (ray_is_atom(v))
old_data = 0;
else if (v->type == RAY_LIST) {
if (v->len < 0) { old_data = 0; }
else old_data = (size_t)ray_len(v) * sizeof(ray_t*);
} else if (v->type == RAY_TABLE || v->type == RAY_DICT) {
old_data = 2 * sizeof(ray_t*);
} else if (RAY_IS_PARTED(v->type) || v->type == RAY_MAPCOMMON) {
int64_t n_ptrs = v->len;
if (v->type == RAY_MAPCOMMON) n_ptrs = 2;
if (n_ptrs < 0) n_ptrs = 0;
old_data = (size_t)n_ptrs * sizeof(ray_t*);
} else {
int8_t t = ray_type(v);
old_data = (t > 0 && t < RAY_TYPE_COUNT && v->len >= 0) ?
(size_t)ray_len(v) * ray_sym_elem_size(t, v->attrs) : 0;
}
if (v->mmod == 0 && v->order >= RAY_ORDER_MIN) {
size_t alloc_data = ray_block_data_bytes(v);
if (old_data > alloc_data) old_data = alloc_data;
}
size_t copy_data = old_data < new_data_size ? old_data : new_data_size;
uint8_t new_mmod = new_v->mmod;
uint8_t new_order = new_v->order;
memcpy(new_v, v, 32 + copy_data);
new_v->mmod = new_mmod;
new_v->order = new_order;
if (RAY_UNLIKELY(ray_rc_sync))
ray_atomic_store(&new_v->rc, 1);
else
new_v->rc = 1;
if (!(v->attrs & RAY_ATTR_ARENA)) {
ray_detach_owned_refs(v);
ray_free(v);
}
}
return new_v;
}
void ray_mem_stats(ray_mem_stats_t* out) {
if (ray_tl_heap)
*out = ray_tl_heap->stats;
else
memset(out, 0, sizeof(*out));
int64_t sc = 0, sp = 0;
ray_sys_get_stat(&sc, &sp);
out->sys_current = (size_t)sc;
out->sys_peak = (size_t)sp;
int64_t mc = 0, mp = 0;
ray_sys_get_mapped(&mc, &mp);
out->sys_mapped = (size_t)mc;
out->sys_mapped_peak = (size_t)mp;
out->direct_bytes = (size_t)atomic_load_explicit(&g_direct_bytes, memory_order_relaxed);
out->direct_count = (size_t)atomic_load_explicit(&g_direct_count, memory_order_relaxed);
}
static ray_heap_t* g_heap_idle = NULL;
static void heap_swap_dir(char* out, size_t cap) {
const char* env = getenv("RAY_HEAP_SWAP");
if (!(env && *env)) env = getenv("TMPDIR");
const char* sp = (env && *env && strlen(env) < cap - 16) ? env : "/tmp";
size_t sp_len = strlen(sp);
memcpy(out, sp, sp_len);
out[sp_len] = '\0';
if (sp_len > 0 && out[sp_len - 1] != '/' && sp_len < cap - 1) {
out[sp_len] = '/';
out[sp_len + 1] = '\0';
}
}
#define RAY_SYS_SPILL_MIN ((size_t)1 << 20)
#if RAY_HEAP_FILE_SPILL
static void* heap_sys_map_file(size_t size) {
static _Atomic uint64_t sys_swap_counter = 0;
char path[320];
heap_swap_dir(path, 256);
size_t plen = strlen(path);
snprintf(path + plen, sizeof(path) - plen, "raysys_%d_%llu.dat", (int)getpid(),
(unsigned long long)atomic_fetch_add_explicit(&sys_swap_counter, 1,
memory_order_relaxed));
int fd = open(path, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
if (fd < 0) return NULL;
void* p = NULL;
if (heap_preallocate(fd, 0, (off_t)size) == 0) {
p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) p = NULL;
}
unlink(path);
close(fd);
if (p) ray_sys_track_add((int64_t)size);
return p;
}
#endif
void* ray_heap_sys_map(size_t size, bool* spilled) {
*spilled = false;
void* p = NULL;
bool big = size >= RAY_SYS_SPILL_MIN;
#if RAY_HEAP_FILE_SPILL
bool to_file = big && heap_anon_would_exceed(size) && heap_make_room(size);
if (!to_file) p = ray_vm_alloc(size);
if (!p && big) {
p = heap_sys_map_file(size);
if (p) { *spilled = true; heap_anon_spilled(); return p; }
}
if (!p && to_file) {
p = ray_vm_alloc(size);
if (p) heap_anon_spilled();
}
#else
p = ray_vm_alloc(size);
#endif
if (p) heap_anon_commit((int64_t)size, big, big);
return p;
}
void ray_heap_sys_unmap(void* p, size_t size, bool spilled) {
if (!p) return;
#if RAY_HEAP_FILE_SPILL
if (spilled) {
munmap(p, size);
ray_sys_track_sub((int64_t)size);
return;
}
#else
(void)spilled;
#endif
atomic_fetch_sub_explicit(&g_anon_committed, (int64_t)size, memory_order_relaxed);
ray_vm_free(p, size);
}
void ray_heap_init(void) {
if (ray_tl_heap) return;
ray_registry_lock();
ray_heap_t* reuse = g_heap_idle;
if (reuse) g_heap_idle = reuse->idle_next;
ray_registry_unlock();
if (reuse) {
reuse->idle_next = NULL;
ray_tl_heap = reuse;
return;
}
size_t heap_sz = (sizeof(ray_heap_t) + 4095) & ~(size_t)4095;
ray_heap_t* h = (ray_heap_t*)ray_vm_alloc(heap_sz);
if (!h) {
fprintf(stderr, "ray_heap_init: ray_vm_alloc(%zu) failed (errno=%d %s) "
"— heap not initialized\n",
heap_sz, errno, strerror(errno));
return;
}
memset(h, 0, heap_sz);
int id = heap_id_acquire();
if (id < 0) {
fprintf(stderr, "ray_heap_init: heap-ID pool exhausted "
"— heap not initialized\n");
ray_vm_free(h, heap_sz);
return;
}
h->id = (uint16_t)id;
ray_registry_lock();
ray_heap_registry[h->id % RAY_HEAP_REGISTRY_SIZE] = h;
ray_registry_unlock();
for (int i = 0; i < RAY_HEAP_FL_SIZE; i++)
fl_init(&h->freelist[i]);
{
size_t budget = RAY_SLAB_BUDGET;
const char* b = getenv("RAY_SLAB_BUDGET");
if (b && *b) {
char* end = NULL;
unsigned long long bv = strtoull(b, &end, 10);
if (end && *end == '\0' && bv > 0) budget = (size_t)bv;
}
for (int i = 0; i < RAY_SLAB_ORDERS; i++) {
size_t bsize = BSIZEOF(RAY_SLAB_MIN + i);
size_t cap = budget / bsize;
if (cap < 1) cap = 1;
if (cap > RAY_SLAB_CACHE_SIZE) cap = RAY_SLAB_CACHE_SIZE;
h->slab_cap[i] = (uint32_t)cap;
}
}
heap_swap_dir(h->swap_path, sizeof(h->swap_path));
ray_tl_heap = h;
}
extern void ray_clear_error_trace(void);
void ray_heap_abandon(void) {
ray_heap_t* h = ray_tl_heap;
if (!h) return;
ray_clear_error_trace();
ray_parted_cache_clear();
heap_drain_foreign(h);
heap_abandon_spill(h);
ray_registry_lock();
h->idle_next = g_heap_idle;
g_heap_idle = h;
ray_registry_unlock();
ray_tl_heap = NULL;
}
void ray_heap_destroy(void) {
ray_heap_t* h = ray_tl_heap;
if (!h) return;
ray_clear_error_trace();
ray_parted_cache_clear();
uint16_t saved_id = h->id;
ray_registry_lock();
ray_heap_registry[h->id % RAY_HEAP_REGISTRY_SIZE] = NULL;
ray_registry_unlock();
atomic_store_explicit(&h->foreign, NULL, memory_order_relaxed);
for (uint32_t i = 0; i < h->pool_count; i++) {
ray_pool_hdr_t* hdr = (ray_pool_hdr_t*)h->pools[i].base;
dfd_purge_range((uintptr_t)h->pools[i].base,
(uintptr_t)h->pools[i].base + BSIZEOF(h->pools[i].pool_order));
ray_vm_free(hdr->vm_base, BSIZEOF(h->pools[i].pool_order));
if (h->pools[i].backed) {
atomic_fetch_sub_explicit(&g_spill_pools, 1, memory_order_relaxed);
if (h->pools[i].swap_fd >= 0) close(h->pools[i].swap_fd);
if (h->pools[i].swap_path) {
unlink(h->pools[i].swap_path);
ray_sys_free(h->pools[i].swap_path);
}
} else {
atomic_fetch_sub_explicit(&g_anon_committed,
(int64_t)BSIZEOF(h->pools[i].pool_order), memory_order_relaxed);
}
}
size_t heap_sz = (sizeof(ray_heap_t) + 4095) & ~(size_t)4095;
ray_vm_free(h, heap_sz);
ray_tl_heap = NULL;
heap_id_release(saved_id);
}
#ifdef DEBUG
static void dfd_validate_freelists(void) {
if (!dfd_enabled()) return;
for (int hid = 0; hid < RAY_HEAP_REGISTRY_SIZE; hid++) {
ray_heap_t* gh = ray_heap_registry[hid];
if (!gh) continue;
for (int ord = RAY_ORDER_MIN; ord < RAY_HEAP_FL_SIZE; ord++) {
ray_fl_head_t* fh = &gh->freelist[ord];
ray_t* blk = fh->fl_next;
int64_t steps = 0;
while (blk != (ray_t*)fh) {
if (!blk) {
fprintf(stderr, "\n=== DFD: NULL link in heap %u freelist order %d ===\n",
gh->id, ord);
dfd_report("freelist NULL link", NULL);
}
if (++steps > (int64_t)1e8) {
fprintf(stderr, "\n=== DFD: cycle in heap %u freelist order %d ===\n",
gh->id, ord);
dfd_report("freelist cycle", blk);
}
blk = blk->fl_next;
}
}
}
}
#endif
#define RAY_FREE_AGE_RELEASE 3
#define RAY_RELEASE_MIN_ORDER 13
#define RAY_RECLAIM_OVERSIZED 1u
#define RAY_RECLAIM_SPILL 2u
#define RAY_RECLAIM_ANON 4u
static bool heap_reclaim_admits(const ray_pool_entry_t* pe, unsigned mode) {
return ((mode & RAY_RECLAIM_OVERSIZED) && pe->pool_order > RAY_HEAP_POOL_ORDER)
|| ((mode & RAY_RECLAIM_SPILL) && pe->backed)
|| ((mode & RAY_RECLAIM_ANON) && !pe->backed);
}
static uint32_t heap_empty_pool_candidates(ray_heap_t* h, unsigned mode,
uint32_t* out) {
if (h->pool_count <= 1) return 0;
if (!(h->avail & (UINT64_MAX << (RAY_HEAP_POOL_ORDER - 1)))) return 0;
uint32_t n = 0;
for (uint32_t p = 0; p < h->pool_count; p++) {
if (h->pools[p].parked || !heap_reclaim_admits(&h->pools[p], mode)) continue;
uintptr_t pb = (uintptr_t)h->pools[p].base;
int k = h->pools[p].pool_order - 1;
for (; k >= RAY_ORDER_MIN; k--) {
ray_t* b = (ray_t*)(pb + BSIZEOF(k));
if (ray_atomic_load(&b->rc) != 0 || b->order != k) break;
}
if (k < RAY_ORDER_MIN) out[n++] = p;
}
return n;
}
static uint32_t heap_find_empty_pools(ray_heap_t* h, unsigned mode, uint8_t* empty) {
uint32_t cand[RAY_MAX_POOLS];
uint32_t n = heap_empty_pool_candidates(h, mode, cand);
if (n == 0) return 0;
uint8_t top = 0;
for (uint32_t i = 0; i < n; i++) {
uint32_t c = cand[i];
uint32_t j = i;
for (; j > 0 && (uintptr_t)h->pools[cand[j - 1]].base
> (uintptr_t)h->pools[c].base; j--)
cand[j] = cand[j - 1];
cand[j] = c;
if (h->pools[c].pool_order > top) top = h->pools[c].pool_order;
}
size_t free_bytes[RAY_MAX_POOLS];
memset(free_bytes, 0, n * sizeof(free_bytes[0]));
for (int ord = RAY_ORDER_MIN; ord < top; ord++) {
if (!(h->avail & (1ULL << ord))) continue;
ray_fl_head_t* fh = &h->freelist[ord];
for (ray_t* blk = fh->fl_next; blk != (ray_t*)fh; blk = blk->fl_next) {
uintptr_t a = (uintptr_t)blk;
uint32_t lo = 0, hi = n;
while (lo < hi) {
uint32_t mid = (lo + hi) / 2;
if ((uintptr_t)h->pools[cand[mid]].base <= a) lo = mid + 1;
else hi = mid;
}
if (lo == 0) continue;
const ray_pool_entry_t* pe = &h->pools[cand[lo - 1]];
if (a < (uintptr_t)pe->base + BSIZEOF(pe->pool_order))
free_bytes[lo - 1] += BSIZEOF(ord);
}
}
uint32_t found = 0;
for (uint32_t i = 0; i < n; i++) {
uint8_t po = h->pools[cand[i]].pool_order;
if (free_bytes[i] == BSIZEOF(po) - BSIZEOF(RAY_ORDER_MIN)) {
empty[cand[i]] = 1;
found++;
}
}
return found;
}
static void heap_pool_unlist(ray_heap_t* h, uint32_t p) {
uintptr_t pb = (uintptr_t)h->pools[p].base;
for (uint8_t k = RAY_ORDER_MIN; k < h->pools[p].pool_order; k++) {
fl_remove((ray_t*)(pb + BSIZEOF(k)));
if (fl_empty(&h->freelist[k]))
h->avail &= ~(1ULL << k);
}
}
static int64_t heap_pool_unmap(ray_heap_t* h, uint32_t p) {
ray_pool_entry_t* pe = &h->pools[p];
uint8_t po = pe->pool_order;
uintptr_t pb = (uintptr_t)pe->base;
int64_t anon = 0;
if (pe->parked) h->parked--;
dfd_purge_range(pb, pb + BSIZEOF(po));
ray_vm_free(((ray_pool_hdr_t*)pb)->vm_base, BSIZEOF(po));
if (pe->backed) {
atomic_fetch_sub_explicit(&g_spill_pools, 1, memory_order_relaxed);
if (pe->swap_fd >= 0) close(pe->swap_fd);
if (pe->swap_path) {
unlink(pe->swap_path);
ray_sys_free(pe->swap_path);
}
} else {
atomic_fetch_sub_explicit(&g_anon_committed,
(int64_t)BSIZEOF(po), memory_order_relaxed);
anon = (int64_t)BSIZEOF(po);
}
h->pools[p] = h->pools[--h->pool_count];
return anon;
}
static int64_t heap_reclaim_empty_pools(ray_heap_t* h, unsigned mode) {
uint8_t empty[RAY_MAX_POOLS];
memset(empty, 0, h->pool_count);
uint32_t n = heap_find_empty_pools(h, mode, empty);
for (uint32_t p = 0; h->parked && p < h->pool_count; p++)
if (h->pools[p].parked && heap_reclaim_admits(&h->pools[p], mode)) {
empty[p] = 1;
n++;
}
if (n == 0) return 0;
int64_t anon = 0;
for (uint32_t p = h->pool_count; p-- > 0; ) {
if (!empty[p]) continue;
if (h->pool_count <= 1) break;
if (!h->pools[p].parked) heap_pool_unlist(h, p);
anon += heap_pool_unmap(h, p);
}
return anon;
}
static void heap_park_empty_pools(ray_heap_t* h, unsigned mode, uint32_t now) {
uint8_t empty[RAY_MAX_POOLS];
memset(empty, 0, h->pool_count);
if (heap_find_empty_pools(h, mode, empty) == 0)
return;
for (uint32_t p = h->pool_count; p-- > 0; ) {
if (!empty[p]) continue;
if (h->pool_count - h->parked <= 1) break;
heap_pool_unlist(h, p);
h->pools[p].parked = 1;
((ray_pool_hdr_t*)h->pools[p].base)->parked_ms = now;
h->parked++;
}
}
static bool heap_unpark(ray_heap_t* h, bool spill) {
for (uint32_t p = h->pool_count; p-- > 0; ) {
ray_pool_entry_t* pe = &h->pools[p];
if (!pe->parked || pe->backed != spill) continue;
uintptr_t pb = (uintptr_t)pe->base;
for (uint8_t o = pe->pool_order; o > RAY_ORDER_MIN; o--)
heap_insert_block(h, (ray_t*)(pb + BSIZEOF(o - 1)), (uint8_t)(o - 1));
pe->parked = 0;
pe->discarded = 0;
h->parked--;
return true;
}
return false;
}
static void heap_unpark_all(ray_heap_t* h) {
while (h->parked && (heap_unpark(h, false) || heap_unpark(h, true))) {}
}
static void heap_abandon_spill(ray_heap_t* h) {
for (uint32_t p = 0; p < h->pool_count; p++)
if (h->pools[p].backed) {
heap_flush_slabs(h);
(void)heap_reclaim_empty_pools(h, RAY_RECLAIM_SPILL);
return;
}
}
static void heap_each_quiescent(void (*fn)(ray_heap_t* h, void* ctx), void* ctx) {
if (ray_tl_heap) fn(ray_tl_heap, ctx);
ray_pool_each_worker_heap(fn, ctx);
}
static void heap_reclaim_oversized(ray_heap_t* h, void* ctx) {
(void)ctx;
(void)heap_reclaim_empty_pools(h, RAY_RECLAIM_OVERSIZED);
}
void ray_heap_gc(void) {
ray_parted_cache_prune();
ray_heap_t* h = ray_tl_heap;
if (!h) return;
#ifdef DEBUG
dfd_validate_freelists();
#endif
bool safe = (atomic_load_explicit(&ray_parallel_flag, memory_order_relaxed) == 0);
heap_drain_foreign(h);
heap_flush_slabs(h);
if (safe) {
heap_each_quiescent(heap_reclaim_oversized, NULL);
#define RAY_P5_VISIT_BUDGET 2048
#define RAY_P5_RELEASE_BUDGET 64
static int s_p5_hid = 0;
static int s_p5_ord = RAY_RELEASE_MIN_ORDER;
uint64_t large_orders_mask = ~((1ULL << 13) - 1);
int64_t p5_visited = 0, p5_released = 0;
int p5_slots = RAY_HEAP_REGISTRY_SIZE * (RAY_HEAP_FL_SIZE - 13);
for (int step = 0; step < p5_slots; step++) {
if (p5_visited >= RAY_P5_VISIT_BUDGET ||
p5_released >= RAY_P5_RELEASE_BUDGET) break;
int hid = s_p5_hid;
int ord = s_p5_ord;
if (++s_p5_ord >= RAY_HEAP_FL_SIZE) {
s_p5_ord = RAY_RELEASE_MIN_ORDER;
s_p5_hid = (s_p5_hid + 1) % RAY_HEAP_REGISTRY_SIZE;
}
ray_heap_t* gh = ray_heap_registry[hid];
if (!gh) continue;
if (!((gh->avail & large_orders_mask) & (1ULL << ord))) continue;
ray_fl_head_t* head = &gh->freelist[ord];
for (ray_t* blk = head->fl_next; blk != (ray_t*)head;
blk = blk->fl_next) {
if (p5_visited++ >= RAY_P5_VISIT_BUDGET) break;
if (blk->attrs < RAY_FREE_AGE_RELEASE) {
blk->attrs++;
} else if (blk->attrs == RAY_FREE_AGE_RELEASE) {
size_t bsize = BSIZEOF(ord);
int rpidx = heap_find_pool(gh, blk);
bool hp = (rpidx >= 0) ? (gh->pools[rpidx].hugepage != 0)
: false;
ray_vm_release_block(blk, bsize, hp);
blk->attrs = RAY_FREE_AGE_RELEASE + 1;
if (++p5_released >= RAY_P5_RELEASE_BUDGET) break;
}
}
}
}
}
static int64_t heap_release_free_pages(ray_heap_t* h) {
int64_t released = 0;
for (int ord = RAY_RELEASE_MIN_ORDER; ord < RAY_HEAP_FL_SIZE; ord++) {
if (!(h->avail & (1ULL << ord))) continue;
ray_fl_head_t* head = &h->freelist[ord];
for (ray_t* blk = head->fl_next; blk != (ray_t*)head;
blk = blk->fl_next) {
if (blk->attrs > RAY_FREE_AGE_RELEASE) continue;
int pidx = heap_find_pool(h, blk);
blk->attrs = RAY_FREE_AGE_RELEASE + 1;
if (pidx >= 0 && h->pools[pidx].backed) continue;
bool hp = (pidx >= 0) ? (h->pools[pidx].hugepage != 0) : false;
ray_vm_release_block(blk, BSIZEOF(ord), hp);
released++;
}
}
return released;
}
static _Atomic(bool) g_spill_discard = true;
static _Atomic(int64_t) g_spill_discard_calls = 0;
bool ray_heap_set_spill_discard(bool on) {
return atomic_exchange_explicit(&g_spill_discard, on, memory_order_relaxed);
}
static int heap_spill_discard(const ray_pool_entry_t* pe) {
#if RAY_HEAP_FILE_SPILL && defined(__linux__) && defined(FALLOC_FL_PUNCH_HOLE)
if (!atomic_load_explicit(&g_spill_discard, memory_order_relaxed)) return -1;
if (!pe) return 0;
long ps = sysconf(_SC_PAGESIZE);
size_t pg = ps > 0 ? (size_t)ps : 4096;
size_t end = ray_vm_resident_end(pe->base, BSIZEOF(pe->pool_order));
if (end <= pg) return 0;
off_t off = (off_t)pg, len = (off_t)(end - pg);
if (fallocate(pe->swap_fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, off, len) != 0
|| heap_preallocate(pe->swap_fd, off, len) != 0)
return -1;
return 1;
#else
(void)pe;
return -1;
#endif
}
int64_t ray_heap_spill_discard_count(void) {
return atomic_load_explicit(&g_spill_discard_calls, memory_order_relaxed);
}
bool ray_heap_spill_discards(void) {
return heap_spill_discard(NULL) == 0;
}
static bool heap_spill_pools_discard(ray_heap_t* h) {
bool dropped = false;
for (uint32_t p = h->pool_count; h->parked && p-- > 0; ) {
ray_pool_entry_t* pe = &h->pools[p];
if (!pe->parked || !pe->backed || pe->discarded) continue;
atomic_fetch_add_explicit(&g_spill_discard_calls, 1, memory_order_relaxed);
int got = heap_spill_discard(pe);
if (got < 0) {
(void)heap_pool_unmap(h, p);
dropped = true;
continue;
}
pe->discarded = 1;
dropped |= got > 0;
}
return dropped;
}
static void heap_spill_pools_age(ray_heap_t* h, uint32_t now) {
int64_t thr = heap_decay_threshold_ms();
for (uint32_t p = h->pool_count; h->parked && h->pool_count > 1 && p-- > 0; ) {
const ray_pool_entry_t* pe = &h->pools[p];
if (pe->parked && pe->backed
&& now - ((const ray_pool_hdr_t*)pe->base)->parked_ms >= (uint64_t)thr)
(void)heap_pool_unmap(h, p);
}
}
static bool heap_make_room(size_t bytes) {
direct_cache_drain();
if (!heap_anon_would_exceed(bytes)) return false;
#if RAY_HEAP_FILE_SPILL
ray_heap_t* h = ray_tl_heap;
if (!h) return true;
heap_drain_foreign(h);
heap_flush_slabs(h);
int64_t got = heap_reclaim_empty_pools(h, RAY_RECLAIM_ANON);
return got == 0 || heap_anon_would_exceed(bytes);
#else
return true;
#endif
}
void ray_heap_release_pages(void) {
ray_heap_t* h = ray_tl_heap;
if (!h) return;
heap_unpark_all(h);
(void)heap_release_free_pages(h);
}
#define RAY_HEAP_DECAY_MS_DEFAULT 10000
#define RAY_HEAP_DECAY_MS_MAX ((int64_t)7 * 24 * 3600 * 1000)
static _Atomic int64_t g_heap_activity_ms;
static _Atomic bool g_heap_decay_armed;
static int64_t g_heap_decay_ms = RAY_HEAP_DECAY_MS_DEFAULT;
void ray_heap_set_decay_ms(int64_t ms) {
if (ms > RAY_HEAP_DECAY_MS_MAX) ms = RAY_HEAP_DECAY_MS_MAX;
g_heap_decay_ms = (ms < 0) ? -1 : ms;
}
static int64_t heap_decay_threshold_ms(void) {
return g_heap_decay_ms;
}
void ray_heap_note_activity(void) {
atomic_store_explicit(&g_heap_activity_ms, ray_time_now_ms(),
memory_order_relaxed);
atomic_store_explicit(&g_heap_decay_armed, true, memory_order_relaxed);
}
int64_t ray_heap_decay_due_ms(void) {
if (!atomic_load_explicit(&g_heap_decay_armed, memory_order_relaxed))
return -1;
int64_t threshold = heap_decay_threshold_ms();
if (threshold < 0) return -1;
int64_t due = atomic_load_explicit(&g_heap_activity_ms,
memory_order_relaxed)
+ threshold - ray_time_now_ms();
return due > 0 ? due : 0;
}
static void heap_decay_sweep(ray_heap_t* h, void* ctx) {
heap_drain_foreign(h);
heap_flush_slabs(h);
heap_unpark_all(h);
(void)heap_reclaim_empty_pools(h, RAY_RECLAIM_SPILL);
*(int64_t*)ctx += heap_release_free_pages(h);
}
int64_t ray_heap_decay(void) {
if (ray_heap_decay_due_ms() != 0) return -1;
if (atomic_load_explicit(&ray_parallel_flag, memory_order_relaxed) != 0)
return -1;
atomic_store_explicit(&g_heap_decay_armed, false, memory_order_relaxed);
int64_t released = 0;
heap_each_quiescent(heap_decay_sweep, &released);
return released;
}
typedef struct { int64_t room, room_other; } relieve_measure_t;
static void heap_relieve_measure(ray_heap_t* h, void* ctx) {
relieve_measure_t* m = ctx;
heap_drain_foreign(h);
heap_flush_slabs(h);
if (h->spilled) return;
uint32_t cand[RAY_MAX_POOLS];
uint32_t n = heap_empty_pool_candidates(h, RAY_RECLAIM_ANON, cand);
uint8_t empty[RAY_MAX_POOLS];
memset(empty, 0, h->pool_count);
for (uint32_t i = 0; i < n; i++) empty[cand[i]] = 1;
int64_t room = 0;
uint32_t gone = 0;
for (uint32_t p = 0; p < h->pool_count; p++) {
const ray_pool_entry_t* pe = &h->pools[p];
if ((empty[p] || pe->parked) && !pe->backed) {
room += (int64_t)BSIZEOF(pe->pool_order);
gone++;
}
}
if (gone && gone == h->pool_count)
room -= (int64_t)BSIZEOF(h->pools[0].pool_order);
m->room += room;
if (h != ray_tl_heap) m->room_other += room;
}
typedef struct { int64_t released; uint32_t now; bool near, act, dropped; } relieve_visit_t;
static void heap_relieve_visit(ray_heap_t* h, void* ctx) {
relieve_visit_t* v = ctx;
if (v->near) {
heap_drain_foreign(h);
heap_flush_slabs(h);
}
int64_t spills = atomic_load_explicit(&g_spill_pools, memory_order_relaxed);
heap_spill_pools_age(h, v->now);
if (v->act) {
(void)heap_reclaim_empty_pools(h, h->spilled ? RAY_RECLAIM_SPILL
: RAY_RECLAIM_SPILL | RAY_RECLAIM_ANON);
if (!h->spilled) v->released += heap_release_free_pages(h);
}
heap_park_empty_pools(h, v->near ? RAY_RECLAIM_ANON | RAY_RECLAIM_SPILL
: RAY_RECLAIM_SPILL, v->now);
v->dropped |= heap_spill_pools_discard(h);
v->dropped |= atomic_load_explicit(&g_spill_pools, memory_order_relaxed) != spills;
h->spilled = 0;
}
enum {
RELIEF_NONE,
RELIEF_JUST,
RELIEF_FUTILE,
};
static _Atomic(int) g_relieve_state = RELIEF_NONE;
static _Atomic(int64_t) g_relieve_wm = 0;
static _Atomic(int64_t) g_relieve_maps = 0;
static _Atomic(int64_t) g_relieve_large = 0;
static _Atomic(int64_t) g_relieve_spills = 0;
static _Atomic(bool) g_relieve_other = false;
int64_t ray_heap_relieve(void) {
if (!RAY_HEAP_FILE_SPILL) return 0;
if (atomic_load_explicit(&ray_parallel_flag, memory_order_relaxed) != 0)
return 0;
const int64_t pool = (int64_t)BSIZEOF(RAY_HEAP_POOL_ORDER);
int64_t wm = heap_anon_watermark();
int64_t committed = atomic_load_explicit(&g_anon_committed, memory_order_relaxed);
int64_t maps = atomic_load_explicit(&g_anon_maps, memory_order_relaxed);
int64_t large = atomic_load_explicit(&g_anon_large, memory_order_relaxed);
int64_t spills = atomic_load_explicit(&g_anon_spills, memory_order_relaxed);
int64_t peak = atomic_exchange_explicit(&g_anon_stmt_peak, committed,
memory_order_relaxed);
bool spilled = atomic_exchange_explicit(&g_relieve_spills, spills,
memory_order_relaxed) != spills;
bool mapped = atomic_exchange_explicit(&g_relieve_maps, maps,
memory_order_relaxed) != maps || spilled;
large -= atomic_exchange_explicit(&g_relieve_large, large, memory_order_relaxed);
bool needed = false;
int state = atomic_load_explicit(&g_relieve_state, memory_order_relaxed);
if (wm != atomic_load_explicit(&g_relieve_wm, memory_order_relaxed))
state = RELIEF_NONE;
else if (mapped && state == RELIEF_JUST) {
needed = large >= pool && peak + pool > wm
&& atomic_load_explicit(&g_relieve_other, memory_order_relaxed);
state = (spilled && large < pool) ? RELIEF_FUTILE : RELIEF_NONE;
} else if (mapped && state == RELIEF_FUTILE && !spilled)
state = RELIEF_NONE;
atomic_store_explicit(&g_relieve_state, state, memory_order_relaxed);
if (wm <= 0) return 0;
bool relieve = (spilled || needed) && state == RELIEF_NONE;
bool near = relieve || spilled || committed + pool > wm;
if (!near && atomic_load_explicit(&g_spill_pools, memory_order_relaxed) == 0)
return 0;
int64_t now = ray_time_now_ms();
if (!near) {
if (now - atomic_load_explicit(&g_far_visit_ms, memory_order_relaxed)
< RAY_FAR_VISIT_MS)
return 0;
atomic_store_explicit(&g_far_visit_ms, now, memory_order_relaxed);
}
relieve_visit_t v = { 0, (uint32_t)now, near, false, false };
if (relieve) {
relieve_measure_t m = { direct_cache_bytes(), 0 };
heap_each_quiescent(heap_relieve_measure, &m);
v.act = m.room >= pool && committed - m.room + pool <= wm;
if (v.act) {
direct_cache_drain();
atomic_store_explicit(&g_relieve_wm, wm, memory_order_relaxed);
atomic_store_explicit(&g_relieve_state, RELIEF_JUST, memory_order_relaxed);
atomic_store_explicit(&g_relieve_other, m.room_other >= pool,
memory_order_relaxed);
}
}
heap_each_quiescent(heap_relieve_visit, &v);
atomic_store_explicit(&g_anon_stmt_peak,
atomic_load_explicit(&g_anon_committed, memory_order_relaxed),
memory_order_relaxed);
if (v.dropped) ray_sys_mem_flush();
return v.released;
}
void ray_heap_merge(ray_heap_t* src) {
ray_heap_t* dst = ray_tl_heap;
if (!dst || !src) return;
heap_unpark_all(src);
dst->stats.alloc_count += src->stats.alloc_count;
dst->stats.free_count += src->stats.free_count;
dst->stats.bytes_allocated += src->stats.bytes_allocated;
dst->stats.slab_hits += src->stats.slab_hits;
dst->stats.direct_count += src->stats.direct_count;
dst->stats.direct_bytes += src->stats.direct_bytes;
if (src->stats.peak_bytes > dst->stats.peak_bytes)
dst->stats.peak_bytes = src->stats.peak_bytes;
for (int i = 0; i < RAY_SLAB_ORDERS; i++) {
while (src->slabs[i].count > 0 && dst->slabs[i].count < dst->slab_cap[i])
dst->slabs[i].stack[dst->slabs[i].count++] =
src->slabs[i].stack[--src->slabs[i].count];
while (src->slabs[i].count > 0) {
ray_t* blk = src->slabs[i].stack[--src->slabs[i].count];
int pidx = heap_find_pool(dst, blk);
uintptr_t pb;
uint8_t po;
if (pidx >= 0) {
pb = (uintptr_t)dst->pools[pidx].base;
po = dst->pools[pidx].pool_order;
} else {
ray_pool_hdr_t* phdr = ray_pool_of(blk);
if (!phdr) continue;
pb = (uintptr_t)phdr;
po = phdr->pool_order;
}
heap_coalesce(dst, blk, pb, po);
}
}
ray_t* fblk = atomic_exchange_explicit(&src->foreign, NULL,
memory_order_acquire);
while (fblk) {
ray_t* next = fblk->fl_next;
int pidx = heap_find_pool(dst, fblk);
uintptr_t pb;
uint8_t po;
if (pidx >= 0) {
pb = (uintptr_t)dst->pools[pidx].base;
po = dst->pools[pidx].pool_order;
} else {
ray_pool_hdr_t* phdr = ray_pool_of(fblk);
if (!phdr) { fblk = next; continue; }
pb = (uintptr_t)phdr;
po = phdr->pool_order;
}
RAY_STAT(dst->stats.bytes_allocated -= BSIZEOF(fblk->order));
heap_coalesce(dst, fblk, pb, po);
fblk = next;
}
for (int i = RAY_ORDER_MIN; i < RAY_HEAP_FL_SIZE; i++) {
if (fl_empty(&src->freelist[i])) continue;
ray_fl_head_t* src_head = &src->freelist[i];
ray_fl_head_t* dst_head = &dst->freelist[i];
ray_t* src_first = src_head->fl_next;
ray_t* src_last = src_head->fl_prev;
ray_t* dst_first = dst_head->fl_next;
dst_head->fl_next = src_first;
src_first->fl_prev = (ray_t*)dst_head;
src_last->fl_next = dst_first;
dst_first->fl_prev = src_last;
dst->avail |= (1ULL << i);
fl_init(src_head);
}
src->avail = 0;
for (uint32_t i = 0; i < src->pool_count; i++) {
if (dst->pool_count < RAY_MAX_POOLS) {
ray_pool_hdr_t* hdr = (ray_pool_hdr_t*)src->pools[i].base;
hdr->heap_id = dst->id;
dst->pools[dst->pool_count++] = src->pools[i];
} else {
fprintf(stderr, "rayforce: ray_heap_merge: pool overflow at "
"RAY_MAX_POOLS (%d)\n", (int)RAY_MAX_POOLS);
abort();
}
}
src->pool_count = 0;
}
void ray_heap_flush_foreign(void) {
ray_heap_t* h = ray_tl_heap;
if (!h) return;
heap_drain_foreign(h);
}
void ray_heap_reclaim_worker(ray_heap_t* h) {
if (!h) return;
if (atomic_load_explicit(&ray_parallel_flag, memory_order_acquire) != 0)
return;
if (!atomic_load_explicit(&h->foreign, memory_order_relaxed)) return;
heap_drain_foreign(h);
}
void ray_heap_push_pending(ray_heap_t* heap) {
if (!heap) return;
ray_registry_lock();
ray_heap_registry[heap->id % RAY_HEAP_REGISTRY_SIZE] = NULL;
ray_registry_unlock();
heap->pending_next = atomic_load_explicit(&ray_heap_pending_merge, memory_order_relaxed);
while (!atomic_compare_exchange_weak_explicit(
&ray_heap_pending_merge,
&heap->pending_next, heap,
memory_order_release, memory_order_relaxed))
;
}
void ray_heap_drain_pending(void) {
ray_heap_t* pending = atomic_exchange_explicit(
&ray_heap_pending_merge, NULL,
memory_order_acquire);
while (pending) {
ray_heap_t* next = pending->pending_next;
ray_heap_merge(pending);
uint16_t saved_id = pending->id;
size_t heap_sz = (sizeof(ray_heap_t) + 4095) & ~(size_t)4095;
ray_vm_free(pending, heap_sz);
heap_id_release(saved_id);
pending = next;
}
}
void* ray_scratch_arena_push(ray_scratch_arena_t* a, size_t nbytes) {
nbytes = (nbytes + 15) & ~(size_t)15;
if (RAY_LIKELY(a->ptr != NULL && a->ptr + nbytes <= a->end))
goto bump;
if (a->n_backing >= RAY_ARENA_MAX_BACKING) return NULL;
size_t block_data = BSIZEOF(RAY_ARENA_BLOCK_ORDER) - 32;
size_t alloc_size = nbytes > block_data ? nbytes : block_data;
ray_t* blk = ray_alloc(alloc_size);
if (!blk) return NULL;
a->backing[a->n_backing++] = blk;
a->ptr = (char*)ray_data(blk);
a->end = a->ptr + ray_block_data_bytes(blk);
bump:;
void* ret = a->ptr;
a->ptr += nbytes;
return ret;
}
void ray_scratch_arena_reset(ray_scratch_arena_t* a) {
for (int i = 0; i < a->n_backing; i++)
ray_free(a->backing[i]);
a->n_backing = 0;
a->ptr = NULL;
a->end = NULL;
}
void ray_parallel_begin(void) { atomic_store(&ray_parallel_flag, 1); }
void ray_parallel_end(void) {
atomic_store(&ray_parallel_flag, 0);
ray_heap_gc();
}
static size_t mapped_block_bytes(const ray_t* v) {
if (v->type == RAY_TABLE || v->type == RAY_DICT || v->type == RAY_LIST) return 0;
{
ray_file_map_t* m = NULL;
if (v->type == RAY_STR && v->str_pool && !RAY_IS_ERR(v->str_pool) &&
v->str_pool->mmod == 3)
m = v->str_pool->file_map;
if (!m) m = ray_file_map_lookup(v);
if (m) return m->len;
}
if (v->type <= 0 || v->type >= RAY_TYPE_COUNT) return 0;
uint8_t esz = ray_sym_elem_size(v->type, v->attrs);
size_t data_size = 32 + (size_t)v->len * esz;
if (v->type == RAY_STR) {
size_t pool_len = 0;
if (v->str_pool && !RAY_IS_ERR(v->str_pool) && v->str_pool->len > 0)
pool_len = (size_t)v->str_pool->len;
data_size += 32 + pool_len;
}
if ((v->attrs & RAY_ATTR_HAS_INDEX) && v->index && !RAY_IS_ERR(v->index)) {
ray_index_t* ix = ray_index_payload(v->index);
if (ix->markers & RAY_MARK_MMAP) {
int64_t region_off = ((int64_t)data_size + 31) & ~(int64_t)31;
data_size = (size_t)(region_off + ray_index_inline_size(ix));
}
}
return (data_size + 4095) & ~(size_t)4095;
}
static size_t block_own_bytes(const ray_t* v) {
if (!v) return 0;
if (v->attrs & RAY_ATTR_ARENA) return 0;
switch (v->mmod) {
case 1: return mapped_block_bytes(v);
case 2: return 0;
case 3: return 0;
default: break;
}
if (ray_is_direct(v)) return ray_direct_map_size(v);
if (v->order < RAY_ORDER_MIN || v->order > RAY_HEAP_MAX_ORDER) return 0;
return BSIZEOF(v->order);
}
static size_t index_own_bytes(const ray_t* idx) {
if (!idx || RAY_IS_ERR(idx) || idx->mmod == 1) return 0;
ray_index_t* ix = ray_index_payload((ray_t*)idx);
if (ix->markers & RAY_MARK_MMAP) return 0;
size_t n = block_own_bytes(idx);
ray_t* kids[4];
int k = ray_index_child_blocks(ix, kids, 4);
for (int i = 0; i < k; i++) n += block_own_bytes(kids[i]);
return n;
}
size_t ray_shallow_bytes(const ray_t* v) {
if (!v) return 0;
size_t n = block_own_bytes(v);
if (RAY_IS_ERR(v)) return n;
if (ray_is_atom(v)) {
if (ray_atom_owns_obj(v) && v->obj && !RAY_IS_ERR(v->obj))
n += block_own_bytes(v->obj);
return n;
}
if (v->attrs & RAY_ATTR_SLICE) return n;
if (v->type == RAY_INDEX) return index_own_bytes(v);
if (v->attrs & RAY_ATTR_HAS_INDEX) n += index_own_bytes(v->index);
if (v->type == RAY_STR && v->str_pool && !RAY_IS_ERR(v->str_pool))
n += block_own_bytes(v->str_pool);
return n;
}
typedef struct { const ray_t** slots; size_t cap; size_t n; } ptrset_t;
static bool ptrset_add(ptrset_t* s, const ray_t* p) {
if (s->n * 2 >= s->cap) {
size_t ncap = s->cap ? s->cap * 2 : 64;
const ray_t** ns = (const ray_t**)ray_sys_alloc(ncap * sizeof(*ns));
if (!ns) return true;
memset(ns, 0, ncap * sizeof(*ns));
for (size_t i = 0; i < s->cap; i++) {
const ray_t* q = s->slots[i];
if (!q) continue;
size_t h = ((uintptr_t)q >> 5) & (ncap - 1);
while (ns[h]) h = (h + 1) & (ncap - 1);
ns[h] = q;
}
if (s->slots) ray_sys_free((void*)s->slots);
s->slots = ns; s->cap = ncap;
}
size_t h = ((uintptr_t)p >> 5) & (s->cap - 1);
while (s->slots[h]) {
if (s->slots[h] == p) return false;
h = (h + 1) & (s->cap - 1);
}
s->slots[h] = p; s->n++;
return true;
}
static size_t retained_walk(const ray_t* v, ptrset_t* seen) {
if (!v || RAY_IS_ERR(v)) return 0;
if (!ptrset_add(seen, v)) return 0;
size_t n = block_own_bytes(v);
if (ray_is_atom(v)) {
if (v->type == RAY_LAMBDA) {
ray_t* lam = (ray_t*)v;
ray_t** slots = (ray_t**)ray_data(lam);
for (int i = 0; i < 4; i++) n += retained_walk(slots[i], seen);
n += retained_walk(LAMBDA_NFO(lam), seen);
n += retained_walk(LAMBDA_DBG(lam), seen);
n += retained_walk(LAMBDA_CLOSURE(lam), seen);
n += retained_walk(LAMBDA_CAPROWS(lam), seen);
return n;
}
if (ray_atom_owns_obj(v)) n += retained_walk(v->obj, seen);
return n;
}
if (v->attrs & RAY_ATTR_SLICE) return n + retained_walk(v->slice_parent, seen);
if (v->type == RAY_INDEX) {
ray_index_t* ix = ray_index_payload((ray_t*)v);
if (v->mmod == 1 || (ix->markers & RAY_MARK_MMAP)) return 0;
ray_t* kids[4];
int k = ray_index_child_blocks(ix, kids, 4);
for (int i = 0; i < k; i++) n += retained_walk(kids[i], seen);
return n;
}
if ((v->attrs & RAY_ATTR_HAS_INDEX) && v->index && !RAY_IS_ERR(v->index) &&
v->index->mmod != 1)
n += retained_walk(v->index, seen);
if (v->type == RAY_STR) {
if (v->str_pool && !RAY_IS_ERR(v->str_pool)) n += retained_walk(v->str_pool, seen);
return n;
}
if (v->type == RAY_SYM) return n;
if (RAY_IS_PARTED(v->type) || v->type == RAY_LIST) {
ray_t** ptrs = (ray_t**)ray_data((ray_t*)v);
for (int64_t i = 0; i < v->len; i++) n += retained_walk(ptrs[i], seen);
return n;
}
if (v->type == RAY_MAPCOMMON || v->type == RAY_TABLE || v->type == RAY_DICT) {
ray_t** slots = (ray_t**)ray_data((ray_t*)v);
n += retained_walk(slots[0], seen);
n += retained_walk(slots[1], seen);
}
return n;
}
size_t ray_retained_bytes(const ray_t* v) {
ptrset_t seen = { NULL, 0, 0 };
size_t n = retained_walk(v, &seen);
if (seen.slots) ray_sys_free((void*)seen.slots);
return n;
}