nova-nix-0.4.0.0: cbits/nn_thunk.c
/*
* nn_thunk.c — Arena-allocated thunk memoization cells.
*
* Chunked bump allocator: linked list of blocks, each containing a
* contiguous array of nn_thunk_t slots. When the current block is
* full, a new block is allocated (same capacity). Pointers into any
* block remain valid until the arena is destroyed.
*
* Memory layout per block:
* [nn_thunk_block header] [slot 0] [slot 1] ... [slot N-1]
*
* Block header uses C99 flexible array member for the slots array,
* so the entire block (header + slots) is one contiguous allocation.
*/
#include "nn_thunk.h"
#include <stdlib.h>
#include <string.h>
/* --- Constants --- */
#define NN_THUNK_DEFAULT_BLOCK_CAPACITY 65536u /* 64k thunks = 1 MB */
/* --- Internal types --- */
struct nn_thunk_block {
struct nn_thunk_block *next; /* NULL for newest block */
uint32_t count; /* slots used in this block */
uint32_t capacity; /* total slots in this block */
nn_thunk_t slots[]; /* C99 flexible array member */
};
struct nn_thunk_arena {
struct nn_thunk_block *head; /* first block (for iteration) */
struct nn_thunk_block *current; /* newest block (for allocation) */
uint32_t total; /* total thunks across all blocks */
uint32_t block_capacity; /* slots per new block */
};
/* --- Global arena --- */
static struct nn_thunk_arena *g_arena = NULL;
/* --- Internal helpers --- */
static struct nn_thunk_block *
alloc_block(uint32_t capacity)
{
struct nn_thunk_block *block = (struct nn_thunk_block *)malloc(
sizeof(struct nn_thunk_block) + (size_t)capacity * sizeof(nn_thunk_t));
if (!block) return NULL;
block->next = NULL;
block->count = 0;
block->capacity = capacity;
return block;
}
static nn_thunk_t *
arena_alloc(struct nn_thunk_arena *arena)
{
/* Current block full — allocate a new one */
if (arena->current->count >= arena->current->capacity) {
struct nn_thunk_block *block = alloc_block(arena->block_capacity);
if (!block) return NULL;
arena->current->next = block;
arena->current = block;
}
nn_thunk_t *t = &arena->current->slots[arena->current->count];
arena->current->count++;
arena->total++;
return t;
}
/* --- Lifecycle --- */
void
nn_thunk_init(uint32_t initial_capacity)
{
/* Destroy existing arena if re-initializing */
if (g_arena) {
nn_thunk_destroy();
}
uint32_t cap = initial_capacity > 0 ? initial_capacity
: NN_THUNK_DEFAULT_BLOCK_CAPACITY;
g_arena = (struct nn_thunk_arena *)malloc(sizeof(struct nn_thunk_arena));
if (!g_arena) return;
struct nn_thunk_block *first = alloc_block(cap);
if (!first) {
free(g_arena);
g_arena = NULL;
return;
}
g_arena->head = first;
g_arena->current = first;
g_arena->total = 0;
g_arena->block_capacity = cap;
}
void
nn_thunk_destroy(void)
{
if (!g_arena) return;
struct nn_thunk_block *block = g_arena->head;
while (block) {
struct nn_thunk_block *next = block->next;
free(block);
block = next;
}
free(g_arena);
g_arena = NULL;
}
/* --- Allocation --- */
nn_thunk_t *
nn_thunk_new(void *pending_data)
{
nn_thunk_t *t = arena_alloc(g_arena);
if (!t) return NULL;
t->state = NN_THUNK_PENDING;
t->val_tag = 0;
t->_pad = 0;
t->bc_idx = 0; /* legacy StablePtr thunk marker */
t->payload = pending_data;
return t;
}
nn_thunk_t *
nn_thunk_new_bc(uint32_t bc_idx, void *env_ptr)
{
nn_thunk_t *t = arena_alloc(g_arena);
if (!t) return NULL;
t->state = NN_THUNK_PENDING;
t->val_tag = 0;
t->_pad = 0;
t->bc_idx = bc_idx;
t->payload = env_ptr;
return t;
}
uint32_t
nn_thunk_get_bc_idx(const nn_thunk_t *thunk)
{
return thunk->bc_idx;
}
void
nn_thunk_set_payload(nn_thunk_t *thunk, void *payload)
{
thunk->payload = payload;
}
nn_thunk_t *
nn_thunk_new_computed(void *value)
{
nn_thunk_t *t = arena_alloc(g_arena);
if (!t) return NULL;
t->state = NN_THUNK_COMPUTED;
t->val_tag = NN_VALUE_PTR;
t->_pad = 0;
t->bc_idx = 0;
t->payload = value;
return t;
}
nn_thunk_t *
nn_thunk_new_computed_int(int64_t value)
{
nn_thunk_t *t = arena_alloc(g_arena);
if (!t) return NULL;
t->state = NN_THUNK_COMPUTED;
t->val_tag = NN_VALUE_INT;
t->_pad = 0;
t->bc_idx = 0;
t->payload = (void *)(intptr_t)value;
return t;
}
nn_thunk_t *
nn_thunk_new_computed_float(double value)
{
nn_thunk_t *t = arena_alloc(g_arena);
if (!t) return NULL;
t->state = NN_THUNK_COMPUTED;
t->val_tag = NN_VALUE_FLOAT;
t->_pad = 0;
t->bc_idx = 0;
memcpy(&t->payload, &value, sizeof(double));
return t;
}
nn_thunk_t *
nn_thunk_new_computed_bool(uint8_t value)
{
nn_thunk_t *t = arena_alloc(g_arena);
if (!t) return NULL;
t->state = NN_THUNK_COMPUTED;
t->val_tag = NN_VALUE_BOOL;
t->_pad = 0;
t->bc_idx = 0;
t->payload = (void *)(intptr_t)value;
return t;
}
nn_thunk_t *
nn_thunk_new_computed_null(void)
{
nn_thunk_t *t = arena_alloc(g_arena);
if (!t) return NULL;
t->state = NN_THUNK_COMPUTED;
t->val_tag = NN_VALUE_NULL;
t->_pad = 0;
t->bc_idx = 0;
t->payload = NULL;
return t;
}
nn_thunk_t *
nn_thunk_new_computed_str(uint32_t symbol)
{
nn_thunk_t *t = arena_alloc(g_arena);
if (!t) return NULL;
t->state = NN_THUNK_COMPUTED;
t->val_tag = NN_VALUE_STR;
t->_pad = 0;
t->bc_idx = 0;
t->payload = (void *)(intptr_t)symbol;
return t;
}
nn_thunk_t *
nn_thunk_new_computed_path(uint32_t symbol)
{
nn_thunk_t *t = arena_alloc(g_arena);
if (!t) return NULL;
t->state = NN_THUNK_COMPUTED;
t->val_tag = NN_VALUE_PATH;
t->_pad = 0;
t->bc_idx = 0;
t->payload = (void *)(intptr_t)symbol;
return t;
}
nn_thunk_t *
nn_thunk_new_computed_list(void *list)
{
nn_thunk_t *t = arena_alloc(g_arena);
if (!t) return NULL;
t->state = NN_THUNK_COMPUTED;
t->val_tag = NN_VALUE_LIST;
t->_pad = 0;
t->bc_idx = 0;
t->payload = list;
return t;
}
nn_thunk_t *
nn_thunk_new_computed_attrs(void *attrset)
{
nn_thunk_t *t = arena_alloc(g_arena);
if (!t) return NULL;
t->state = NN_THUNK_COMPUTED;
t->val_tag = NN_VALUE_ATTRS;
t->_pad = 0;
t->bc_idx = 0;
t->payload = attrset;
return t;
}
nn_thunk_t *
nn_thunk_new_computed_ctxstr(void *ctxstr)
{
nn_thunk_t *t = arena_alloc(g_arena);
if (!t) return NULL;
t->state = NN_THUNK_COMPUTED;
t->val_tag = NN_VALUE_CTXSTR;
t->_pad = 0;
t->bc_idx = 0;
t->payload = ctxstr;
return t;
}
nn_thunk_t *
nn_thunk_new_computed_lambda(void *lambda)
{
nn_thunk_t *t = arena_alloc(g_arena);
if (!t) return NULL;
t->state = NN_THUNK_COMPUTED;
t->val_tag = NN_VALUE_LAMBDA;
t->_pad = 0;
t->bc_idx = 0;
t->payload = lambda;
return t;
}
/* --- State queries --- */
uint8_t
nn_thunk_state(const nn_thunk_t *thunk)
{
return thunk->state;
}
void *
nn_thunk_payload(const nn_thunk_t *thunk)
{
return thunk->payload;
}
uint8_t
nn_thunk_value_tag(const nn_thunk_t *thunk)
{
return thunk->val_tag;
}
int64_t
nn_thunk_get_int(const nn_thunk_t *thunk)
{
return (int64_t)(intptr_t)thunk->payload;
}
double
nn_thunk_get_float(const nn_thunk_t *thunk)
{
double result;
memcpy(&result, &thunk->payload, sizeof(double));
return result;
}
uint8_t
nn_thunk_get_bool(const nn_thunk_t *thunk)
{
return (uint8_t)(intptr_t)thunk->payload;
}
uint32_t
nn_thunk_get_str(const nn_thunk_t *thunk)
{
return (uint32_t)(intptr_t)thunk->payload;
}
uint32_t
nn_thunk_get_path(const nn_thunk_t *thunk)
{
return (uint32_t)(intptr_t)thunk->payload;
}
void *
nn_thunk_get_list(const nn_thunk_t *thunk)
{
return thunk->payload;
}
void *
nn_thunk_get_attrs(const nn_thunk_t *thunk)
{
return thunk->payload;
}
void *
nn_thunk_get_ctxstr(const nn_thunk_t *thunk)
{
return thunk->payload;
}
void *
nn_thunk_get_lambda(const nn_thunk_t *thunk)
{
return thunk->payload;
}
/* --- State transitions --- */
int
nn_thunk_mark_blackhole(nn_thunk_t *thunk)
{
if (thunk->state != NN_THUNK_PENDING) return 0;
thunk->state = NN_THUNK_BLACKHOLE;
return 1;
}
void *
nn_thunk_set_computed(nn_thunk_t *thunk, void *value)
{
if (thunk->state == NN_THUNK_COMPUTED) return NULL;
void *old = thunk->payload;
thunk->state = NN_THUNK_COMPUTED;
thunk->val_tag = NN_VALUE_PTR;
thunk->payload = value;
return old;
}
void *
nn_thunk_set_computed_int(nn_thunk_t *thunk, int64_t value)
{
if (thunk->state == NN_THUNK_COMPUTED) return NULL;
void *old = thunk->payload;
thunk->state = NN_THUNK_COMPUTED;
thunk->val_tag = NN_VALUE_INT;
thunk->payload = (void *)(intptr_t)value;
return old;
}
void *
nn_thunk_set_computed_float(nn_thunk_t *thunk, double value)
{
if (thunk->state == NN_THUNK_COMPUTED) return NULL;
void *old = thunk->payload;
thunk->state = NN_THUNK_COMPUTED;
thunk->val_tag = NN_VALUE_FLOAT;
memcpy(&thunk->payload, &value, sizeof(double));
return old;
}
void *
nn_thunk_set_computed_bool(nn_thunk_t *thunk, uint8_t value)
{
if (thunk->state == NN_THUNK_COMPUTED) return NULL;
void *old = thunk->payload;
thunk->state = NN_THUNK_COMPUTED;
thunk->val_tag = NN_VALUE_BOOL;
thunk->payload = (void *)(intptr_t)value;
return old;
}
void *
nn_thunk_set_computed_null(nn_thunk_t *thunk)
{
if (thunk->state == NN_THUNK_COMPUTED) return NULL;
void *old = thunk->payload;
thunk->state = NN_THUNK_COMPUTED;
thunk->val_tag = NN_VALUE_NULL;
thunk->payload = NULL;
return old;
}
void *
nn_thunk_set_computed_str(nn_thunk_t *thunk, uint32_t symbol)
{
if (thunk->state == NN_THUNK_COMPUTED) return NULL;
void *old = thunk->payload;
thunk->state = NN_THUNK_COMPUTED;
thunk->val_tag = NN_VALUE_STR;
thunk->payload = (void *)(intptr_t)symbol;
return old;
}
void *
nn_thunk_set_computed_path(nn_thunk_t *thunk, uint32_t symbol)
{
if (thunk->state == NN_THUNK_COMPUTED) return NULL;
void *old = thunk->payload;
thunk->state = NN_THUNK_COMPUTED;
thunk->val_tag = NN_VALUE_PATH;
thunk->payload = (void *)(intptr_t)symbol;
return old;
}
void *
nn_thunk_set_computed_list(nn_thunk_t *thunk, void *list)
{
if (thunk->state == NN_THUNK_COMPUTED) return NULL;
void *old = thunk->payload;
thunk->state = NN_THUNK_COMPUTED;
thunk->val_tag = NN_VALUE_LIST;
thunk->payload = list;
return old;
}
void *
nn_thunk_set_computed_attrs(nn_thunk_t *thunk, void *attrset)
{
if (thunk->state == NN_THUNK_COMPUTED) return NULL;
void *old = thunk->payload;
thunk->state = NN_THUNK_COMPUTED;
thunk->val_tag = NN_VALUE_ATTRS;
thunk->payload = attrset;
return old;
}
void *
nn_thunk_set_computed_ctxstr(nn_thunk_t *thunk, void *ctxstr)
{
if (thunk->state == NN_THUNK_COMPUTED) return NULL;
void *old = thunk->payload;
thunk->state = NN_THUNK_COMPUTED;
thunk->val_tag = NN_VALUE_CTXSTR;
thunk->payload = ctxstr;
return old;
}
void *
nn_thunk_set_computed_lambda(nn_thunk_t *thunk, void *lambda)
{
if (thunk->state == NN_THUNK_COMPUTED) return NULL;
void *old = thunk->payload;
thunk->state = NN_THUNK_COMPUTED;
thunk->val_tag = NN_VALUE_LAMBDA;
thunk->payload = lambda;
return old;
}
/* --- Arena diagnostics / cleanup iteration --- */
uint32_t
nn_thunk_count(void)
{
return g_arena ? g_arena->total : 0;
}
nn_thunk_t *
nn_thunk_get(uint32_t index)
{
if (!g_arena || index >= g_arena->total) return NULL;
struct nn_thunk_block *block = g_arena->head;
while (block) {
if (index < block->count) {
return &block->slots[index];
}
index -= block->count;
block = block->next;
}
return NULL; /* unreachable if index < total */
}
/* --- Bulk StablePtr collection (single block-list pass, O(total)) --- */
uint32_t
nn_thunk_count_stableptrs(void)
{
uint32_t count = 0;
struct nn_thunk_block *block;
uint32_t i;
if (!g_arena) return 0;
for (block = g_arena->head; block; block = block->next) {
for (i = 0; i < block->count; i++) {
const nn_thunk_t *t = &block->slots[i];
if (t->state == NN_THUNK_PENDING || t->state == NN_THUNK_BLACKHOLE) {
if (t->payload) count++;
} else if (t->state == NN_THUNK_COMPUTED && t->val_tag == NN_VALUE_PTR) {
if (t->payload) count++;
}
}
}
return count;
}
uint32_t
nn_thunk_collect_stableptrs(void **output, uint32_t max_count)
{
uint32_t written = 0;
struct nn_thunk_block *block;
uint32_t i;
if (!g_arena) return 0;
for (block = g_arena->head; block && written < max_count; block = block->next) {
for (i = 0; i < block->count && written < max_count; i++) {
const nn_thunk_t *t = &block->slots[i];
if (t->state == NN_THUNK_PENDING || t->state == NN_THUNK_BLACKHOLE) {
if (t->payload) output[written++] = t->payload;
} else if (t->state == NN_THUNK_COMPUTED && t->val_tag == NN_VALUE_PTR) {
if (t->payload) output[written++] = t->payload;
}
}
}
return written;
}