nova-nix-0.2.0.0: cbits/nn_thunk.h
/*
* nn_thunk.h — Arena-allocated thunk memoization cells for nova-nix.
*
* Thunks are the core memoization mechanism in Nix evaluation. Each
* thunk starts as PENDING (holding a bytecode index + C environment
* pointer) and transitions to COMPUTED (holding the result value) on
* first force. A BLACKHOLE state detects infinite recursion.
*
* Replaces Haskell's IORef ThunkCell (~56 bytes on GHC heap, all GC-
* traced) with a 16-byte C struct in an arena (invisible to GHC GC).
* For 8M thunks: ~450 MB less GHC heap pressure.
*
* Arena: chunked bump allocator. Thunks are never individually freed —
* the entire arena is destroyed at evaluation end. Pointers into the
* arena remain valid until nn_thunk_destroy().
*
* PENDING payloads are StablePtr Env (~16 bytes on GHC heap). The
* StablePtr is necessary for knot-tying: deferred env construction in
* recursive lets/attrs requires Haskell laziness. Freed on force.
* COMPUTED payloads are either inline scalars (int/float/bool/null via
* val_tag 0-3), C-native pointers (str/path/list/attrs/ctxstr/lambda
* via val_tag 4-9), or StablePtrs (for VBuiltin/VDrv via tag 255).
*
* Lifecycle: nn_thunk_init() before evaluation, nn_thunk_destroy()
* after. Not thread-safe — single-threaded evaluation only.
*/
#ifndef NN_THUNK_H
#define NN_THUNK_H
#include <stdint.h>
/* --- Thunk states --- */
#define NN_THUNK_PENDING 0
#define NN_THUNK_COMPUTED 1
#define NN_THUNK_BLACKHOLE 2
/* --- Value tags (valid when state == COMPUTED) --- */
#define NN_VALUE_INT 0 /* payload = (void*)(intptr_t)int64_value */
#define NN_VALUE_FLOAT 1 /* payload = memcpy'd double */
#define NN_VALUE_BOOL 2 /* payload = (void*)(intptr_t)(0 or 1) */
#define NN_VALUE_NULL 3 /* payload = NULL */
#define NN_VALUE_STR 4 /* payload = (void*)(intptr_t)nn_symbol_t (no context) */
#define NN_VALUE_PATH 5 /* payload = (void*)(intptr_t)nn_symbol_t */
#define NN_VALUE_LIST 6 /* payload = nn_list_t* */
#define NN_VALUE_ATTRS 7 /* payload = nn_attrset_t* */
#define NN_VALUE_CTXSTR 8 /* payload = nn_ctxstr_t* (string with context) */
#define NN_VALUE_LAMBDA 9 /* payload = nn_lambda_t* (lambda closure) */
#define NN_VALUE_PTR 255 /* payload = StablePtr NixValue (complex types) */
/* --- Types --- */
/* A thunk: mutable cell holding either a pending expression or a
* computed value. 16 bytes: state(1) + val_tag(1) + _pad(2) +
* bc_idx(4) + payload(8). val_tag is valid only when state == COMPUTED.
*
* PENDING: bc_idx = bytecode instruction index
* payload = StablePtr Env (Haskell-side, for knot-tying)
* COMPUTED: val_tag selects interpretation of payload:
* INT/BOOL: (intptr_t) cast of scalar
* FLOAT: memcpy'd double
* NULL: ignored
* PTR: StablePtr to Haskell NixValue (complex types)
* BLACKHOLE: bc_idx/payload = stale from PENDING (do not dereference) */
typedef struct nn_thunk {
uint8_t state;
uint8_t val_tag;
uint16_t _pad;
uint32_t bc_idx; /* PENDING: bytecode index; COMPUTED: 0 */
void *payload;
} nn_thunk_t;
/* Nix integers are int64_t, stored inline via (intptr_t) cast into payload.
* This requires pointers to be at least 64 bits. 32-bit targets would
* silently truncate large integers. C99-compatible compile-time check: */
typedef char nn_thunk_ptr_size_check_[(sizeof(void *) >= sizeof(int64_t)) ? 1 : -1];
/* --- Lifecycle --- */
/* Initialize the global thunk arena. initial_capacity is the number
* of thunks to pre-allocate per block (0 uses default: 65536 = 1 MB).
* Must be called once before any nn_thunk_new() calls. */
void nn_thunk_init(uint32_t initial_capacity);
/* Destroy the global thunk arena, freeing all block memory.
* Does NOT free payload StablePtrs — caller must iterate and free
* them first (via nn_thunk_count/nn_thunk_get).
* All nn_thunk_t pointers become invalid after this call. */
void nn_thunk_destroy(void);
/* --- Allocation --- */
/* Allocate a new PENDING thunk from the arena (legacy StablePtr path).
* pending_data is an opaque pointer (StablePtr to Haskell value).
* Sets bc_idx = 0 to distinguish from bytecode thunks.
* Returns a pointer into arena memory, valid until nn_thunk_destroy(). */
nn_thunk_t *nn_thunk_new(void *pending_data);
/* Allocate a new PENDING thunk with a bytecode index + env payload.
* bc_idx is the root instruction index in the bytecode store.
* env_ptr is a StablePtr Env from Haskell (required for knot-tying;
* raw Ptr would force the env at allocation time, breaking laziness). */
nn_thunk_t *nn_thunk_new_bc(uint32_t bc_idx, void *env_ptr);
/* Read the bytecode index from a PENDING thunk. */
uint32_t nn_thunk_get_bc_idx(const nn_thunk_t *thunk);
/* Set the payload of a thunk. Used for deferred env fixup in
* knot-tying (rec attrs, let, formal defaults): allocate the thunk
* with NULL payload first, then fill the env pointer once the
* knot-tied env is constructed. */
void nn_thunk_set_payload(nn_thunk_t *thunk, void *payload);
/* Allocate a new pre-COMPUTED thunk from the arena (StablePtr payload).
* value is an opaque pointer (StablePtr to Haskell NixValue).
* val_tag is set to NN_VALUE_PTR. */
nn_thunk_t *nn_thunk_new_computed(void *value);
/* Allocate pre-COMPUTED thunks with inline scalar values (no StablePtr). */
nn_thunk_t *nn_thunk_new_computed_int(int64_t value);
nn_thunk_t *nn_thunk_new_computed_float(double value);
nn_thunk_t *nn_thunk_new_computed_bool(uint8_t value);
nn_thunk_t *nn_thunk_new_computed_null(void);
/* Allocate pre-COMPUTED thunks with C-native complex values (no StablePtr). */
nn_thunk_t *nn_thunk_new_computed_str(uint32_t symbol);
nn_thunk_t *nn_thunk_new_computed_path(uint32_t symbol);
nn_thunk_t *nn_thunk_new_computed_list(void *list);
nn_thunk_t *nn_thunk_new_computed_attrs(void *attrset);
nn_thunk_t *nn_thunk_new_computed_ctxstr(void *ctxstr);
nn_thunk_t *nn_thunk_new_computed_lambda(void *lambda);
/* --- State queries --- */
/* Read the current state (NN_THUNK_PENDING/COMPUTED/BLACKHOLE). */
uint8_t nn_thunk_state(const nn_thunk_t *thunk);
/* Read the payload pointer. Meaning depends on state + val_tag. */
void *nn_thunk_payload(const nn_thunk_t *thunk);
/* Read the value tag (valid when state == COMPUTED). */
uint8_t nn_thunk_value_tag(const nn_thunk_t *thunk);
/* Read inline scalar values from a COMPUTED thunk. */
int64_t nn_thunk_get_int(const nn_thunk_t *thunk);
double nn_thunk_get_float(const nn_thunk_t *thunk);
uint8_t nn_thunk_get_bool(const nn_thunk_t *thunk);
/* Read C-native complex values from a COMPUTED thunk. */
uint32_t nn_thunk_get_str(const nn_thunk_t *thunk);
uint32_t nn_thunk_get_path(const nn_thunk_t *thunk);
void *nn_thunk_get_list(const nn_thunk_t *thunk);
void *nn_thunk_get_attrs(const nn_thunk_t *thunk);
void *nn_thunk_get_ctxstr(const nn_thunk_t *thunk);
void *nn_thunk_get_lambda(const nn_thunk_t *thunk);
/* --- State transitions --- */
/* Transition PENDING -> BLACKHOLE (thunk is being evaluated).
* Payload remains unchanged (caller reads it before this call).
* Returns 1 on success, 0 if thunk is not PENDING. */
int nn_thunk_mark_blackhole(nn_thunk_t *thunk);
/* Set a non-COMPUTED thunk to COMPUTED with a StablePtr value (NN_VALUE_PTR).
* Accepts PENDING or BLACKHOLE state (skips blackhole for direct memoization).
* Returns the old payload (pending StablePtr for caller to free).
* Returns NULL if thunk is already COMPUTED. */
void *nn_thunk_set_computed(nn_thunk_t *thunk, void *value);
/* Set a non-COMPUTED thunk to COMPUTED with inline scalar values.
* Returns the old payload (pending StablePtr for caller to free).
* Returns NULL if thunk is already COMPUTED. */
void *nn_thunk_set_computed_int(nn_thunk_t *thunk, int64_t value);
void *nn_thunk_set_computed_float(nn_thunk_t *thunk, double value);
void *nn_thunk_set_computed_bool(nn_thunk_t *thunk, uint8_t value);
void *nn_thunk_set_computed_null(nn_thunk_t *thunk);
/* Set a non-COMPUTED thunk to COMPUTED with C-native complex values.
* Returns the old payload (pending StablePtr for caller to free).
* Returns NULL if thunk is already COMPUTED. */
void *nn_thunk_set_computed_str(nn_thunk_t *thunk, uint32_t symbol);
void *nn_thunk_set_computed_path(nn_thunk_t *thunk, uint32_t symbol);
void *nn_thunk_set_computed_list(nn_thunk_t *thunk, void *list);
void *nn_thunk_set_computed_attrs(nn_thunk_t *thunk, void *attrset);
void *nn_thunk_set_computed_ctxstr(nn_thunk_t *thunk, void *ctxstr);
void *nn_thunk_set_computed_lambda(nn_thunk_t *thunk, void *lambda);
/* --- Arena diagnostics / cleanup iteration --- */
/* Total thunks allocated across all blocks. */
uint32_t nn_thunk_count(void);
/* Get a thunk by global index (0-based, across all blocks).
* Used for StablePtr cleanup iteration before nn_thunk_destroy().
* Index must be < nn_thunk_count(). */
nn_thunk_t *nn_thunk_get(uint32_t index);
#endif /* NN_THUNK_H */