nova-nix-0.4.0.0: cbits/nn_env.c
/*
* nn_env.c — C-native evaluation environments.
*
* Arena-allocated nn_env_t structs and backing arrays. Pages are
* 256 KB each, allocated on demand as a linked list. Each allocation
* bumps a pointer within the current page.
*
* All memory is freed in bulk via nn_env_destroy().
*/
#include "nn_env.h"
#include "nn_assert.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* --- Constants --- */
#define NN_ENV_PAGE_SIZE (256u * 1024u) /* 256 KB per page */
#define NN_ENV_ALIGN 8u /* 8-byte alignment */
/* --- Internal types --- */
struct nn_env_page {
struct nn_env_page *next; /* NULL for newest page */
uint32_t used; /* bytes used in this page */
uint32_t capacity; /* usable bytes in this page */
/* Flexible array member: page data follows */
char data[];
};
/* --- Global state --- */
static struct nn_env_page *g_first_page = NULL;
static struct nn_env_page *g_current_page = NULL;
static uint64_t g_total_bytes = 0;
/* Global empty env — initialized in nn_env_init, valid until destroy. */
static nn_env_t g_empty_env;
/* --- Internal helpers --- */
static struct nn_env_page *
alloc_page(uint32_t capacity)
{
struct nn_env_page *page = (struct nn_env_page *)malloc(
sizeof(struct nn_env_page) + (size_t)capacity);
if (!page) return NULL;
page->next = NULL;
page->used = 0;
page->capacity = capacity;
return page;
}
static uint32_t
align_up(uint32_t n, uint32_t alignment)
{
return (n + alignment - 1) & ~(alignment - 1);
}
/* Allocate raw bytes from the page-based bump allocator.
* Returns aligned, zero-initialized memory. */
static void *
nn_env_alloc_raw(uint32_t bytes)
{
bytes = align_up(bytes, NN_ENV_ALIGN);
if (!g_current_page || g_current_page->used + bytes > g_current_page->capacity) {
uint32_t page_cap = bytes > NN_ENV_PAGE_SIZE ? bytes : NN_ENV_PAGE_SIZE;
struct nn_env_page *page = alloc_page(page_cap);
if (!page) return NULL;
if (g_current_page) {
g_current_page->next = page;
} else {
g_first_page = page;
}
g_current_page = page;
}
void *result = g_current_page->data + g_current_page->used;
memset(result, 0, bytes);
g_current_page->used += bytes;
g_total_bytes += bytes;
return result;
}
/* --- Lifecycle --- */
void
nn_env_init(void)
{
if (g_first_page) {
nn_env_destroy();
}
g_first_page = alloc_page(NN_ENV_PAGE_SIZE);
if (!g_first_page) { fprintf(stderr, "nn_env_init: page alloc failed\n"); abort(); }
g_current_page = g_first_page;
g_total_bytes = 0;
/* Initialize global empty env */
memset(&g_empty_env, 0, sizeof(g_empty_env));
}
void
nn_env_destroy(void)
{
struct nn_env_page *page = g_first_page;
while (page) {
struct nn_env_page *next = page->next;
free(page);
page = next;
}
g_first_page = NULL;
g_current_page = NULL;
g_total_bytes = 0;
}
/* --- Slot allocation --- */
void **
nn_env_alloc_slots(uint32_t count)
{
if (count == 0) return NULL;
uint64_t bytes64 = (uint64_t)count * sizeof(void *);
if (bytes64 > UINT32_MAX) return NULL;
uint32_t bytes = (uint32_t)bytes64;
return (void **)nn_env_alloc_raw(bytes);
}
/* --- Env constructors --- */
nn_env_t *
nn_env_empty(void)
{
return &g_empty_env;
}
nn_env_t *
nn_env_new(void **slots, uint32_t slot_count,
void *lazy_scope, nn_env_t *parent,
void **with_scopes, uint16_t with_count)
{
nn_env_t *env = (nn_env_t *)nn_env_alloc_raw((uint32_t)sizeof(nn_env_t));
if (!env) return NULL;
env->slots = slots;
env->slot_count = slot_count;
env->lazy_scope = lazy_scope;
env->parent = parent;
env->with_scopes = with_scopes;
env->with_count = with_count;
return env;
}
nn_env_t *
nn_env_from_slots(void **slots, uint32_t slot_count,
nn_env_t *parent)
{
nn_env_t *env = (nn_env_t *)nn_env_alloc_raw((uint32_t)sizeof(nn_env_t));
if (!env) return NULL;
env->slots = slots;
env->slot_count = slot_count;
env->lazy_scope = NULL;
env->parent = parent;
env->with_scopes = parent ? parent->with_scopes : NULL;
env->with_count = parent ? parent->with_count : 0;
return env;
}
nn_env_t *
nn_env_push_with(nn_env_t *base, void *scope)
{
if (base->with_count >= UINT16_MAX) return NULL;
uint16_t new_count = base->with_count + 1;
void **new_withs = (void **)nn_env_alloc_raw(
(uint32_t)new_count * (uint32_t)sizeof(void *));
if (!new_withs) return NULL;
/* New scope at index 0 (innermost) */
new_withs[0] = scope;
/* Copy existing with-scopes after it */
if (base->with_count > 0 && base->with_scopes) {
memcpy(new_withs + 1, base->with_scopes,
(size_t)base->with_count * sizeof(void *));
}
nn_env_t *env = (nn_env_t *)nn_env_alloc_raw((uint32_t)sizeof(nn_env_t));
if (!env) return NULL;
env->slots = base->slots;
env->slot_count = base->slot_count;
env->lazy_scope = base->lazy_scope;
env->parent = base->parent;
env->with_scopes = new_withs;
env->with_count = new_count;
return env;
}
nn_env_t *
nn_env_new_minimal(void **slots, uint32_t slot_count)
{
nn_env_t *env = (nn_env_t *)nn_env_alloc_raw((uint32_t)sizeof(nn_env_t));
if (!env) return NULL;
env->slots = slots;
env->slot_count = slot_count;
/* lazy_scope, parent, with_scopes, with_count already zero from alloc_raw */
return env;
}
/* --- Accessors --- */
void **
nn_env_slots(const nn_env_t *env)
{
return env->slots;
}
uint32_t
nn_env_slot_count(const nn_env_t *env)
{
return env->slot_count;
}
void *
nn_env_lazy_scope(const nn_env_t *env)
{
return env->lazy_scope;
}
nn_env_t *
nn_env_parent(const nn_env_t *env)
{
return env->parent;
}
void **
nn_env_with_scopes(const nn_env_t *env)
{
return env->with_scopes;
}
uint16_t
nn_env_with_count(const nn_env_t *env)
{
return env->with_count;
}
/* --- Lookup --- */
void *
nn_env_lookup_resolved(const nn_env_t *env, int level, int idx)
{
NN_ASSERT(env != NULL, "nn_env_lookup_resolved: NULL env");
if (env == NULL) {
return NULL;
}
while (level > 0) {
NN_ASSERT(env->parent != NULL, "nn_env_lookup_resolved: parent chain too short for level");
if (env->parent == NULL) {
return NULL;
}
env = env->parent;
level--;
}
/* The resolver supplies these indices; guard the slot read in release
builds too (NDEBUG strips the assert) so a resolver bug surfaces as a
NULL rather than an out-of-bounds read past the slot array. */
NN_ASSERT(idx >= 0 && (uint32_t)idx < env->slot_count, "nn_env_lookup_resolved: idx out of bounds");
if (idx < 0 || (uint32_t)idx >= env->slot_count) {
return NULL;
}
return env->slots[idx];
}
void *
nn_env_root_scope(const nn_env_t *env)
{
while (env->parent) {
env = env->parent;
}
return env->lazy_scope;
}
/* --- With-scopes array allocation --- */
void **
nn_env_alloc_with_scopes(uint16_t count)
{
if (count == 0) return NULL;
uint64_t bytes64 = (uint64_t)count * sizeof(void *);
if (bytes64 > UINT32_MAX) return NULL;
uint32_t bytes = (uint32_t)bytes64;
return (void **)nn_env_alloc_raw(bytes);
}
/* --- Diagnostics --- */
uint64_t
nn_env_bytes_allocated(void)
{
return g_total_bytes;
}