tables, threads and LUTs

This commit is contained in:
2026-06-19 13:47:56 +02:00
parent 9c0a40a336
commit 0e39f0e0b5
5 changed files with 512 additions and 17 deletions

View File

@@ -190,7 +190,8 @@ static noom_Exit noomC_compile_expr(
return NOOM_PLEASEHELPMEIAMSCARED; // 😭😭😭😭😭😭😭😭
}
if (node->type == NOOMP_NODE_STRINGLITERAL) {
unsigned char fucking_destination = compiler->curstack++;
compiler->curstack++;
unsigned char fucking_destination = func->constsize;
noom_Exit result = noomC_addconst_str(compiler, vm, "but DID YOU KNOW", 16);
if(result) return result;
return noomC_emit_Aus(func, NOOMV_INSTR_PUSHCONST, 0, fucking_destination);

View File

@@ -112,6 +112,8 @@ int the_theoretical_function_to_execute_your_code_that_should_be_replaced_later(
printf("%s %d ", dis.name, inst.a);
switch(dis.arg) {
case NOOMV_DIS_NONE:
break;
case NOOMV_DIS_BC:
printf("%d, %d", inst.b, inst.c);
break;

View File

@@ -227,6 +227,13 @@ noom_Exit noom_return(noom_LuaVM* vm, noom_uint_t retc);
noom_Exit noom_yieldk(noom_LuaVM* vm, noom_uint_t n, noom_KFunction* f, void* ctx);
#define noom_yield(vm, n) noom_yieldk((vm), (n), 0, 0)
// push the current error handler
noom_Exit noom_pusherrhandler(noom_LuaVM *vm);
// pops the top value and sets it as the error handler.
// nil is a perfectly fine error handler, it just does not get called.
// In the case of an error, noom_callk() will also call this error handler.
noom_Exit noom_seterrhandler(noom_LuaVM *vm);
// Like a noom_call, except instead of calling, it resumes coroutines.
// This cannot yield but allows yields.
noom_Exit noom_resume(noom_LuaVM* vm, noom_uint_t argc, noom_uint_t retc);

465
src/vm.c
View File

@@ -1,10 +1,17 @@
#include "vm.h"
#include "helper.h"
#include <assert.h>
#include <stdio.h>
const noomV_Value noomV_nil = {
.tag = NOOMV_VNIL,
.isptr = 0,
.autoclose = 0,
};
noomV_DisInfo noomV_disInfo[NOOMV_INSTR_NOP2] = {
[NOOMV_INSTR_PUSHNIL] = {
.name = "PUSHNIL",
[NOOMV_INSTR_PUSHVAL] = {
.name = "PUSHVAL",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_PUSHCONST] = {
@@ -15,10 +22,136 @@ noomV_DisInfo noomV_disInfo[NOOMV_INSTR_NOP2] = {
.name = "PUSHGLOBAL",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_PUSHINT] = {
.name = "PUSHINT",
.arg = NOOMV_DIS_sD,
},
[NOOMV_INSTR_PUSHNIL] = {
.name = "PUSHNIL",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_PUSHBOOLS] = {
.name = "PUSHBOOLS",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_PUSHUPVAL] = {
.name = "PUSHUPVAL",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_PUSHARGS] = {
.name = "PUSHARGS",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_CREATETABLE] = {
.name = "CREATETABLE",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_PUSHCLOSURE] = {
.name = "PUSHCLOSURE",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_CALL] = {
.name = "CALL",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_GETTABLE] = {
.name = "GETTABLE",
.arg = NOOMV_DIS_NONE,
},
// pops (table, field, value), and sets `table[field] = value`
[NOOMV_INSTR_SETTABLE] = {
.name = "SETTABLE",
.arg = NOOMV_DIS_NONE,
},
[NOOMV_INSTR_SETLIST] = {
.name = "SETLIST",
.arg = NOOMV_DIS_uD,
},
// pops table, pushes `table[consts[op.uD]]`, an optimization for indexing fields
[NOOMV_INSTR_GETFIELD] = {
.name = "GETFIELD",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_SETFIELD] = {
.name = "SETFIELD",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_GETUPFIELD] = {
.name = "GETUPFIELD",
.arg = NOOMV_DIS_uD,
},
[NOOMV_ISNTR_SETUPFIELD] = {
.name = "SETUPFIELD",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_OP] = {
.name = "OP",
.arg = NOOMV_DIS_BC,
},
[NOOMV_INSTR_SETVAL] = {
.name = "SETVAL",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_SETUPVAL] = {
.name = "SETUPVAL",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_SETGLOBAL] = {
.name = "SETGLOBAL",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_RET] = {
.name = "RET",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_JMP] = {
.name = "JMP",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_CJMP] = {
.name = "CJMP",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_CNJMP] = {
.name = "CNJMP",
.arg = NOOMV_DIS_uD,
},
// For bullshit
// Take the top value without popping it, push `value[consts[op.uD]]` like GETFIELD would, then swap the top 2 values.
// This is for a:foo() and such
[NOOMV_INSTR_GETMETHOD] = {
.name = "GETMETHOD",
.arg = NOOMV_DIS_uD,
},
// rotate the the top op.a items by op.sD
[NOOMV_INSTR_ROTATE] = {
.name = "ROTATE",
.arg = NOOMV_DIS_sD,
},
[NOOMV_INSTR_POP] = {
.name = "POP",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_SETSTACK] = {
.name = "SETSTACK",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_CONCAT] = {
.name = "CONCAT",
.arg = NOOMV_DIS_uD,
},
[NOOMV_INSTR_CLOSE] = {
.name = "CLOSE",
.arg = NOOMV_DIS_uD,
},
};
noomV_Object* noomV_allocObj(noom_LuaVM* vm, noomV_ObjTag tag, noom_uint_t size) {
@@ -38,6 +171,8 @@ noomV_String* noomV_allocStr(noom_LuaVM* vm, const char* str, noom_uint_t len) {
if (s == 0) return 0;
noom_memcpy(s->data, str, len);
s->data[len] = '\0';
s->len = len;
s->hash = 0;
return s;
}
@@ -63,19 +198,253 @@ noomV_Function* noomV_allocFunc(noom_LuaVM* vm, noomV_String* chunkname) {
return f;
}
noomV_Table* noomV_allocTable(noom_LuaVM* vm) {
noomV_Table* t = (noomV_Table*)noomV_allocObj(vm, NOOMV_OTABLE, sizeof(noomV_Table));
if (t == 0) return 0;
t->meta = 0;
t->entries = 0;
t->used = 0;
noomV_Table *noomV_allocTable(noom_LuaVM *vm, noom_uint_t arraylen, noom_uint_t fields) {
noomV_Table *t = (noomV_Table *)noomV_allocObj(vm, NOOMV_OTABLE, sizeof(noomV_Table));
if(t == 0) return 0;
// not computed
t->len = 0;
t->entrydata = 0;
// no metatable
t->meta = 0;
// no entries filled
t->used = 0;
// bit of space between fields in case of hash collisions to not blast them into O(N) hell
noom_uint_t cap = fields * 4 + arraylen;
t->entries = 0;
t->entrydata = noom_alloc(sizeof(noomV_Value) * 2 * cap);
// dw about freeing t, its in the VM heap so it'll get GC'd out
if(t->entrydata == 0) return 0;
t->entries = cap;
for(size_t i = 0; i < cap*2; i++) {
t->entrydata[i].tag = NOOMV_VNIL;
}
return t;
}
noom_uint_t noomV_rawhashValue(noomV_Value v) {
switch(v.tag) {
case NOOMV_VNIL:
return 0;
case NOOMV_VINT:
return v.integer;
case NOOMV_VBOOL:
return v.boolean ? 1 : 0;
case NOOMV_VNUM:
// TODO: see if we should bitcast instead
return v.number;
case NOOMV_VCFUNC:
return (noom_uint_t)v.cfunc;
case NOOMV_VLUSER:
return (noom_uint_t)v.lightuserdata;
case NOOMV_VOBJ: {
noomV_Object *o = v.obj;
if(o->tag != NOOMV_OSTR) return (noom_uint_t)o;
noomV_String *s = (noomV_String *)o;
if(s->hash == 0) {
// https://en.wikipedia.org/wiki/Jenkins_hash_function#one_at_a_time
noom_uint_t hash = 0;
for(noom_uint_t i = 0; i < s->len; i++) {
hash += (unsigned char)s->data[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
s->hash = hash;
}
return s->hash;
}
}
}
noom_bool_t noomV_isNil(noomV_Value key) { return key.tag == NOOMV_VNIL; }
noom_bool_t noomV_isLegalKey(noomV_Value key) {
if(key.tag == NOOMV_VNIL) return 0;
if(key.tag == NOOMV_VNUM) {
noom_float_t n = key.number;
// true for all except NaN, which is an illegal key!
return n == n;
}
return 1;
}
noom_bool_t noomV_rawequalValue(noomV_Value a, noomV_Value b) {
// special case: integers and numbers
if(a.tag == NOOMV_VINT && b.tag == NOOMV_VNUM) {
return a.integer == b.number;
}
if(a.tag == NOOMV_VNUM && b.tag == NOOMV_VINT) {
return a.number == b.integer;
}
if(a.tag != b.tag) return 0;
switch(a.tag) {
case NOOMV_VNIL:
return 1;
case NOOMV_VINT:
return a.integer == b.integer;
case NOOMV_VBOOL:
return (a.boolean != 0) == (b.boolean != 0);
case NOOMV_VNUM:
return a.number == b.number;
case NOOMV_VCFUNC:
return a.cfunc == b.cfunc;
case NOOMV_VLUSER:
return a.lightuserdata == b.lightuserdata;
case NOOMV_VOBJ: {
noomV_Object *aObj = a.obj;
noomV_Object *bObj = b.obj;
if(aObj == bObj) return 1;
if(aObj->tag != bObj->tag) return 0;
if(aObj->tag != NOOMV_OSTR) return aObj == bObj;
noomV_String *strA = (noomV_String *)aObj;
noomV_String *strB = (noomV_String *)bObj;
if(strA->len != strB->len) return 0;
if(strA->hash != 0 && strB->hash != 0 && strA->hash != strB->hash) return 0;
for(noom_uint_t i = 0; i < strA->len; i++) {
if(strA->data[i] != strB->data[i]) return 0;
}
return 1;
}
}
}
noomV_Value noomV_rawgetTable(noomV_Table *t, noomV_Value key) {
if(!noomV_isLegalKey(key)) return noomV_nil;
noom_uint_t hash = noomV_rawhashValue(key);
noom_uint_t start = hash % t->entries;
for(noom_uint_t i = 0; i < t->entries; i++) {
noom_uint_t idx = (start + i) % t->entries;
// unallocated entry, bye-bye
if(t->entrydata[idx].tag == NOOMV_VNIL) break;
// isptr on a key means its a tombstone
if(t->entrydata[idx].isptr) continue;
if(noomV_rawequalValue(t->entrydata[idx], key)) {
return t->entrydata[idx + t->entries];
}
}
return noomV_nil;
}
noomV_Value noomV_rawgetiTable(noomV_Table *t, noom_int_t idx) {
return noomV_rawgetTable(t, (noomV_Value) { .tag = NOOMV_VINT, .integer = idx });
}
// TODO: implement
// used should be at most ~80% of entrydata because
// that's standard practice and we're not computer scientists
noom_Exit noomV_rawsetTable(noom_LuaVM *vm, noomV_Table *t, noomV_Value key, noomV_Value val) {
if(!noomV_isLegalKey(key)) {
noomV_setErrorStr(vm, vm->currentThread, "illegal key");
return NOOM_ERUNTIME;
}
noom_uint_t target = t->entries * 80 / 100;
if(t->used > target) {
// TODO: resize hashtable
}
// prevent awkward bugs
key.isptr = 0;
key.autoclose = 0;
val.isptr = 0;
val.autoclose = 0;
noom_uint_t hash = noomV_rawhashValue(key);
noom_uint_t idx = hash % t->entries;
while(1) {
noomV_Value key2 = t->entrydata[idx];
// tombstone!
if(key2.isptr) {
t->entrydata[idx] = key;
t->entrydata[idx + t->entries] = val;
return NOOM_OK;
}
if(noomV_rawequalValue(key, key2)) {
// HOLY SHIET!!!!!
t->entrydata[idx + t->entries] = val;
return NOOM_OK;
}
idx++;
idx %= t->entries;
}
return NOOM_OK;
}
noom_uint_t noomV_rawlenTable(noomV_Table *t) {
noom_uint_t guess = t->len;
// TODO: optimize with a bsearch
while(1) {
if(!noomV_isNil(noomV_rawgetiTable(t, guess+1))) guess++;
if(guess == 0) break;
if(noomV_isNil(noomV_rawgetiTable(t, guess))) guess--;
}
return t->len = guess;
}
noomV_Thread *noomV_allocCoroutine(noom_LuaVM *vm) {
noomV_Thread *thrd = (noomV_Thread *)noomV_allocObj(vm, NOOMV_OTHREAD, sizeof(noomV_Thread));
if(thrd == 0) return 0;
thrd->resumedBy = 0;
thrd->resuming = 0;
thrd->calldepth = 0;
thrd->stacklen = 0;
thrd->stackcap = 32;
thrd->callcap = 4;
thrd->errObj = noomV_nil;
thrd->calls = noom_alloc(sizeof(noomV_CallFrame) * thrd->callcap);
if(thrd->calls == 0) return 0;
thrd->stack = noom_alloc(sizeof(noomV_Value) * thrd->stackcap);
if(thrd->stack == 0) return 0;
return thrd;
}
void noomV_setErrorStr(noom_LuaVM *vm, noomV_Thread *coro, const char *str) {
noomV_String *s = noomV_allocStr(vm, str, noom_strlen(str));
if(s == 0) {
coro->errObj = vm->oomVal;
return;
}
coro->errObj.tag = NOOMV_VOBJ;
coro->errObj.obj = &s->obj;
}
noom_Exit noomV_setErrorFromExit(noom_LuaVM *vm, noomV_Thread *coro, noom_Exit exit) {
switch(exit) {
case NOOM_OK:
coro->errObj = noomV_nil;
break;
case NOOM_PLEASEHELPMEIAMSCARED:
noomV_setErrorStr(vm, coro, "temporary error");
break;
case NOOM_IAMNOTSCAREDJUSTLAZYTHISTIME:
noomV_setErrorStr(vm, coro, "programmer laziness error");
break;
case NOOM_EINTERNAL:
noomV_setErrorStr(vm, coro, "unrecoverable internal error");
break;
case NOOM_ENOMEM:
coro->errObj = vm->oomVal;
break;
case NOOM_ERUNTIME:
break;
case NOOM_ENOSTACK:
noomV_setErrorStr(vm, coro, "stack overflow");
break;
case NOOM_EIO:
noomV_setErrorStr(vm, coro, "I/O error");
break;
case NOOM_ELIMIT:
noomV_setErrorStr(vm, coro, "buffer overflow");
break;
case NOOM_EERROR:
break;
}
return exit;
}
void noomV_freeObj(noomV_Object* obj) {
printf("%d\n", obj->tag);
if(obj->tag == NOOMV_OFUNC) {
noomV_Function *f = (noomV_Function *)obj;
noom_free(f->consts);
@@ -83,21 +452,97 @@ void noomV_freeObj(noomV_Object* obj) {
noom_free(f->locals);
noom_free(f->code);
}
if(obj->tag == NOOMV_OTABLE) {
noomV_Table *t = (noomV_Table *)obj;
noom_free(t->entrydata);
}
if(obj->tag == NOOMV_OTHREAD) {
noomV_Thread *thrd = (noomV_Thread *)obj;
noom_free(thrd->stack);
noom_free(thrd->calls);
}
noom_free(obj);
}
noom_Exit noomV_pushCallFrame(noom_LuaVM *vm, noomV_Thread *coro, noomV_CallFrame cf);
noomV_CallFrame *noomV_topCallFrame(noomV_Thread *coro) {
if(coro->calldepth == 0) return 0;
return &coro->calls[coro->calldepth - 1];
}
noom_Exit noomV_setThreadStackSize(noom_LuaVM *vm, noomV_Thread *coro, noom_int_t stack) {
if(stack < 0) {
noomV_setErrorStr(vm, coro, "stack underflow");
return NOOM_ERUNTIME;
}
if(stack > NOOM_MAXSTACK) return NOOM_ELIMIT;
if(stack > coro->stackcap) {
noom_uint_t newCap = coro->stackcap;
while(newCap < stack) newCap *= 2;
noomV_Value *newStack = noom_realloc(coro->stack, sizeof(noomV_Value) * coro->stackcap);
if(newStack == 0) return NOOM_ENOMEM;
coro->stack = newStack;
coro->stackcap = newCap;
}
// NIL-ify
if(coro->stacklen < stack) {
for(int i = coro->stacklen; i < stack; i++) {
coro->stack[i] = noomV_nil;
}
return NOOM_OK;
}
// close. TODO: actually loop to run __close!
// Preferably figure out some way to do a linked list of TBC values
coro->stacklen = stack;
return NOOM_OK;
}
noom_LuaVM* noom_createVM(noom_LuaVersion version) {
noom_LuaVM* vm = noom_alloc(sizeof(*vm));
if (vm == 0) return 0;
// initialize the universe to NULL, handles partial OOMs nicely
vm->heap = 0;
vm->graySet = 0;
vm->oomVal.tag = NOOMV_VOBJ;
vm->oomVal.obj = 0;
vm->globals = 0;
vm->registry = 0;
vm->mainThread = 0;
vm->currentThread = 0;
vm->version = version;
const char *oomStr = "out of memory";
vm->oomVal.obj = (noomV_Object *)noomV_allocStr(vm, oomStr, noom_strlen(oomStr));
if(vm->oomVal.obj == 0) goto rip;
vm->registry = noomV_allocTable(vm, 0, 1);
if(vm->registry == 0) goto rip;
vm->globals = noomV_allocTable(vm, 0, 32);
if(vm->globals == 0) goto rip;
vm->mainThread = noomV_allocCoroutine(vm);
if(vm->mainThread == 0) goto rip;
vm->currentThread = vm->mainThread;
// goal: put _G in the registry as the _G key
noomV_String *_G = noomV_allocStr(vm, "_G", 2);
if(_G == 0) goto rip;
noomV_Value _GKey = { .tag = NOOMV_VOBJ, .obj = &_G->obj, .isptr = 0, .autoclose = 0 };
noomV_Value _GVal = { .tag = NOOMV_VOBJ, .obj = &vm->globals->obj, .isptr = 0, .autoclose = 0 };
noom_Exit err = noomV_rawsetTable(vm, vm->registry, _GKey, _GVal);
if(err) goto rip;
assert(noomV_rawequalValue(noomV_rawgetTable(vm->registry, _GKey), _GVal));
return vm;
rip:
noom_destroyVM(vm);
return 0;
}
void noom_destroyVM(noom_LuaVM* vm) {

View File

@@ -20,6 +20,7 @@ typedef enum noomV_ObjTag {
NOOMV_OTABLE,
NOOMV_OUSERDATA,
NOOMV_OPOINTER,
NOOMV_OTHREAD,
} noomV_ObjTag;
typedef struct noomV_Object {
@@ -49,6 +50,7 @@ typedef struct noomV_Value {
noom_bool_t boolean;
noom_int_t integer;
noom_float_t number;
noom_CFunction *cfunc;
void* lightuserdata;
noomV_Object* obj;
struct noomV_Pointer* ptr;
@@ -57,6 +59,7 @@ typedef struct noomV_Value {
typedef struct noomV_String {
noomV_Object obj;
noom_uint_t hash;
noom_uint_t len;
// Includes NUL-terminator!
char data[];
@@ -105,12 +108,12 @@ typedef enum noomV_Opcode : unsigned char {
NOOMV_INSTR_PUSHARGS,
// Pushes a new table. op.uD is the initial capacity
NOOMV_INSTR_CREATETABLE,
// Pushes a closure, using the prototype in protos[op.uD]
// Pushes a closure, using the prototype in `protos[op.uD]`
NOOMV_INSTR_PUSHCLOSURE,
// High-level ops
// Call the function stored at stack[op.a]. All values after that are treated at arguments. op.uD is the expected return count plus one, and if op.uD is 0,
// Call the function stored at `stack[op.a]`. All values after that are treated at arguments. op.uD is the expected return count plus one, and if op.uD is 0,
// all returns are pushed.
NOOMV_INSTR_CALL,
@@ -147,7 +150,8 @@ typedef enum noomV_Opcode : unsigned char {
// Control flow
// returns from a function. The amount returned is from op.uD
// returns from a function. The first value returned is in op.uD.
// If that is out of bounds, nothing is returned.
NOOMV_INSTR_RET,
// computes T = op.a << 16 + op.uD, jumps to T.
@@ -164,7 +168,7 @@ typedef enum noomV_Opcode : unsigned char {
NOOMV_INSTR_GETMETHOD,
// rotate the the top op.a items by op.sD
NOOMV_INSTR_ROTATE,
// pop op.uD values
// pop op.uD+1 values
NOOMV_INSTR_POP,
// set the stack size to op.uD, inserting nils if need be, good for labels
NOOMV_INSTR_SETSTACK,
@@ -181,6 +185,7 @@ typedef enum noomV_Opcode : unsigned char {
typedef struct noomV_DisInfo {
const char *name;
enum {
NOOMV_DIS_NONE,
NOOMV_DIS_BC,
NOOMV_DIS_uD,
NOOMV_DIS_sD,
@@ -303,13 +308,18 @@ typedef struct noomV_Function {
unsigned char localsize;
} noomV_Function;
// exactly 64 bytes, aka one cache line.
// TODO: ensure it is 64-byte aligned for a perfect 1:1 struct to cache line ratio for perf
typedef struct noomV_CallFrame {
// stack index of function
unsigned int funcIdx;
noom_uint_t funcIdx;
noom_uint_t returnCount;
noom_bool_t isC;
noomV_Value errhandler;
noomV_Pointer **upvals;
union {
struct {
//
// program counter
unsigned int pc;
// amount of varargs
unsigned int varargc;
@@ -325,6 +335,7 @@ typedef struct noomV_Thread {
noomV_Object obj;
unsigned int stacklen, calldepth;
unsigned int stackcap, callcap;
noomV_Value errObj;
// can have pointers!
noomV_Value* stack;
noomV_CallFrame* calls;
@@ -335,6 +346,7 @@ typedef struct noomV_Thread {
struct noom_LuaVM {
noomV_Object* heap;
noomV_Object* graySet;
noomV_Value oomVal;
noomV_Table* globals;
noomV_Table* registry;
noomV_Thread* mainThread;
@@ -342,10 +354,38 @@ struct noom_LuaVM {
noom_LuaVersion version;
};
// for conveniently passing around nil
extern const noomV_Value noomV_nil;
// Allocating objects
noomV_Object* noomV_allocObj(noom_LuaVM* vm, noomV_ObjTag tag, noom_uint_t size);
noomV_String* noomV_allocStr(noom_LuaVM* vm, const char* str, noom_uint_t len);
noomV_Function* noomV_allocFunc(noom_LuaVM* vm, noomV_String* chunkname);
noomV_Table *noomV_allocTable(noom_LuaVM *vm, noom_uint_t arraylen, noom_uint_t fields);
noom_uint_t noomV_rawhashValue(noomV_Value v);
noom_bool_t noomV_isNil(noomV_Value key);
noom_bool_t noomV_isLegalKey(noomV_Value key);
noom_bool_t noomV_rawequalValue(noomV_Value a, noomV_Value b);
noomV_Value noomV_rawgetTable(noomV_Table *t, noomV_Value key);
noomV_Value noomV_rawgetiTable(noomV_Table *t, noom_int_t idx);
noom_Exit noomV_rawsetTable(noom_LuaVM *vm, noomV_Table *t, noomV_Value key, noomV_Value val);
noom_uint_t noomV_rawlenTable(noomV_Table *t);
noomV_Thread *noomV_allocCoroutine(noom_LuaVM *vm);
void noomV_setErrorStr(noom_LuaVM *vm, noomV_Thread *coro, const char *str);
// if exit is ERUNTIME or EERROR, does nothing as it assumes the error is already set!
// It will clear any errors on NOOM_OK!!!
// Returns the exit for convenience
noom_Exit noomV_setErrorFromExit(noom_LuaVM *vm, noomV_Thread *coro, noom_Exit exit);
void noomV_freeObj(noomV_Object* obj);
noom_Exit noomV_pushCallFrame(noom_LuaVM *vm, noomV_Thread *coro, noomV_CallFrame cf);
// can return NULL if we are not in a call, such as in C API setup.
noomV_CallFrame *noomV_topCallFrame(noomV_Thread *coro);
noom_Exit noomV_setThreadStackSize(noom_LuaVM *vm, noomV_Thread *coro, noom_int_t stack);
#endif