nova-nix-0.1.9.0: cbits/nn_bytecode.c
/*
* nn_bytecode.c -- Flat bytecode storage for Nix expressions.
*
* Two growable arrays:
* - g_ops: nn_op_t instructions (16 bytes each), indexed by uint32
* - g_data: uint32_t values for variable-length operands
*
* Both use realloc-doubling. Write-once during compilation, random
* access during evaluation. Indices remain stable across realloc.
*/
#include "nn_bytecode.h"
#include "nn_assert.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* --- Constants --- */
#define NN_BC_DEFAULT_OP_CAPACITY 65536u
#define NN_BC_DEFAULT_DATA_CAPACITY 131072u
/* --- Global state --- */
static nn_op_t *g_ops = NULL;
static uint32_t g_op_count = 0;
static uint32_t g_op_capacity = 0;
static uint32_t *g_data = NULL;
static uint32_t g_data_count = 0;
static uint32_t g_data_capacity = 0;
/* --- Lifecycle --- */
void nn_bytecode_init(uint32_t op_capacity, uint32_t data_capacity)
{
if (g_ops) nn_bytecode_destroy();
if (op_capacity == 0) op_capacity = NN_BC_DEFAULT_OP_CAPACITY;
if (data_capacity == 0) data_capacity = NN_BC_DEFAULT_DATA_CAPACITY;
g_ops = (nn_op_t *)malloc((size_t)op_capacity * sizeof(nn_op_t));
if (!g_ops) { fprintf(stderr, "nn_bytecode_init: ops alloc failed\n"); abort(); }
g_op_count = 0;
g_op_capacity = op_capacity;
g_data = (uint32_t *)malloc((size_t)data_capacity * sizeof(uint32_t));
if (!g_data) { fprintf(stderr, "nn_bytecode_init: data alloc failed\n"); abort(); }
g_data_count = 0;
g_data_capacity = data_capacity;
}
void nn_bytecode_destroy(void)
{
free(g_ops);
g_ops = NULL;
g_op_count = 0;
g_op_capacity = 0;
free(g_data);
g_data = NULL;
g_data_count = 0;
g_data_capacity = 0;
}
/* --- Internal: grow arrays --- */
static int ensure_op_space(void)
{
if (g_op_count < g_op_capacity) return 0;
uint32_t new_cap = g_op_capacity * 2;
if (new_cap < g_op_capacity) return -1; /* overflow guard */
nn_op_t *new_ops = (nn_op_t *)realloc(g_ops, (size_t)new_cap * sizeof(nn_op_t));
if (!new_ops) return -1;
g_ops = new_ops;
g_op_capacity = new_cap;
return 0;
}
static int ensure_data_space(void)
{
if (g_data_count < g_data_capacity) return 0;
uint32_t new_cap = g_data_capacity * 2;
if (new_cap < g_data_capacity) return -1; /* overflow guard */
uint32_t *new_data = (uint32_t *)realloc(g_data, (size_t)new_cap * sizeof(uint32_t));
if (!new_data) return -1;
g_data = new_data;
g_data_capacity = new_cap;
return 0;
}
/* --- Emit --- */
uint32_t nn_bc_emit(uint8_t opcode, uint8_t flags, uint16_t short_arg,
uint32_t arg1, uint32_t arg2, uint32_t arg3)
{
if (ensure_op_space() != 0) return UINT32_MAX;
uint32_t idx = g_op_count++;
nn_op_t *op = &g_ops[idx];
op->opcode = opcode;
op->flags = flags;
op->short_arg = short_arg;
op->arg1 = arg1;
op->arg2 = arg2;
op->arg3 = arg3;
return idx;
}
uint32_t nn_bc_emit_data(uint32_t value)
{
if (ensure_data_space() != 0) return UINT32_MAX;
uint32_t offset = g_data_count++;
g_data[offset] = value;
return offset;
}
/* --- Read instructions --- */
uint8_t nn_bc_opcode(uint32_t idx) { NN_ASSERT(idx < g_op_count, "nn_bc_opcode: idx out of bounds"); return g_ops[idx].opcode; }
uint8_t nn_bc_flags(uint32_t idx) { NN_ASSERT(idx < g_op_count, "nn_bc_flags: idx out of bounds"); return g_ops[idx].flags; }
uint16_t nn_bc_short_arg(uint32_t idx) { NN_ASSERT(idx < g_op_count, "nn_bc_short_arg: idx out of bounds"); return g_ops[idx].short_arg; }
uint32_t nn_bc_arg1(uint32_t idx) { NN_ASSERT(idx < g_op_count, "nn_bc_arg1: idx out of bounds"); return g_ops[idx].arg1; }
uint32_t nn_bc_arg2(uint32_t idx) { NN_ASSERT(idx < g_op_count, "nn_bc_arg2: idx out of bounds"); return g_ops[idx].arg2; }
uint32_t nn_bc_arg3(uint32_t idx) { NN_ASSERT(idx < g_op_count, "nn_bc_arg3: idx out of bounds"); return g_ops[idx].arg3; }
/* --- Read data --- */
uint32_t nn_bc_data(uint32_t offset) { NN_ASSERT(offset < g_data_count, "nn_bc_data: offset out of bounds"); return g_data[offset]; }
/* --- Diagnostics --- */
uint32_t nn_bc_op_count(void) { return g_op_count; }
uint32_t nn_bc_data_count(void) { return g_data_count; }