392 lines
18 KiB
C
392 lines
18 KiB
C
#ifndef NOOM_VM
|
|
#define NOOM_VM
|
|
|
|
#include "noom.h"
|
|
|
|
#ifndef NOOM_MAXSTACK
|
|
#define NOOM_MAXSTACK 4096
|
|
#endif
|
|
|
|
#ifndef NOOM_MAXCALL
|
|
#define NOOM_MAXCALL (NOOM_MAXSTACK / 32)
|
|
#endif
|
|
|
|
// Defines values, threads, bullshit, idc
|
|
|
|
typedef enum noomV_ObjTag {
|
|
NOOMV_OSTR,
|
|
NOOMV_OFUNC,
|
|
NOOMV_OCLOSURE,
|
|
NOOMV_OTABLE,
|
|
NOOMV_OUSERDATA,
|
|
NOOMV_OPOINTER,
|
|
NOOMV_OTHREAD,
|
|
} noomV_ObjTag;
|
|
|
|
typedef struct noomV_Object {
|
|
noomV_ObjTag tag;
|
|
noom_bool_t marked;
|
|
struct noomV_Object* next;
|
|
struct noomV_Object* nextGray;
|
|
} noomV_Object;
|
|
|
|
typedef enum noomV_ValueTag : unsigned char {
|
|
NOOMV_VNIL,
|
|
NOOMV_VBOOL,
|
|
NOOMV_VINT,
|
|
NOOMV_VNUM,
|
|
NOOMV_VLUSER,
|
|
NOOMV_VCFUNC,
|
|
NOOMV_VOBJ,
|
|
} noomV_ValueTag;
|
|
|
|
typedef struct noomV_Value {
|
|
noomV_ValueTag tag;
|
|
// for stack slots
|
|
noom_bool_t autoclose;
|
|
// pointer to value
|
|
noom_bool_t isptr;
|
|
union {
|
|
noom_bool_t boolean;
|
|
noom_int_t integer;
|
|
noom_float_t number;
|
|
noom_CFunction *cfunc;
|
|
void* lightuserdata;
|
|
noomV_Object* obj;
|
|
struct noomV_Pointer* ptr;
|
|
};
|
|
} noomV_Value;
|
|
|
|
typedef struct noomV_String {
|
|
noomV_Object obj;
|
|
noom_uint_t hash;
|
|
noom_uint_t len;
|
|
// Includes NUL-terminator!
|
|
char data[];
|
|
} noomV_String;
|
|
|
|
typedef struct noomV_Table {
|
|
noomV_Object obj;
|
|
struct noomV_Table* meta;
|
|
// amount of entries allocated
|
|
noom_uint_t entries;
|
|
// how many entries are defined.
|
|
// Note, this includes keys with null values, which are still scanned.
|
|
noom_uint_t used;
|
|
// cache of #t, for faster subsequent fetches
|
|
noom_uint_t len;
|
|
// actual values. Given index i, V[i] is the key and V[i+entries] is the value.
|
|
// This is because we mostly read the key, so we should put more keys in cache.
|
|
noomV_Value* entrydata;
|
|
} noomV_Table;
|
|
|
|
typedef struct noomV_Pointer {
|
|
noomV_Object obj;
|
|
noomV_Value value;
|
|
} noomV_Pointer;
|
|
|
|
typedef enum noomV_Opcode : unsigned char {
|
|
NOOMV_INSTR_NOP = 0,
|
|
|
|
// Push data on the stack
|
|
|
|
// Pushes the value at `stack[op.uD]`
|
|
NOOMV_INSTR_PUSHVAL,
|
|
// Pushes `consts[op.uD]`
|
|
NOOMV_INSTR_PUSHCONST,
|
|
// Pushes `_G[consts[op.uD]]`, Lua 5.1 only.
|
|
NOOMV_INSTR_PUSHGLOBAL,
|
|
// Pushes op.sD itself as an integer
|
|
NOOMV_INSTR_PUSHINT,
|
|
// Pushes [op.uD+1] nils
|
|
NOOMV_INSTR_PUSHNIL,
|
|
// Pushes [op.a+1] booleans, encoded in [op.uD], where the ith (0-indexed) boolean is encoded in `uD & (1 << i)`
|
|
NOOMV_INSTR_PUSHBOOLS,
|
|
// Pushes *upvals[op.uD]
|
|
NOOMV_INSTR_PUSHUPVAL,
|
|
// Pushes op.a varargs. If op.a is 0, it will push all of them, regardless of how many
|
|
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]`
|
|
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,
|
|
// all returns are pushed.
|
|
NOOMV_INSTR_CALL,
|
|
|
|
// pops (table, field), and pushes `table[field]`
|
|
NOOMV_INSTR_GETTABLE,
|
|
// pops (table, field, value), and sets `table[field] = value`
|
|
NOOMV_INSTR_SETTABLE,
|
|
// pops op.uD values, then sets it as an array starting at op.a+1 in the table at the new top;
|
|
// if that is confused, it basically does this:
|
|
// int table = stacksize - op.uD - 1;
|
|
// for(int i = 0; i < op.uD; i++) `stack[table][op.a + 1 + i] = stack[table + i + 1];`
|
|
NOOMV_INSTR_SETLIST,
|
|
// pops table, pushes `table[consts[op.uD]]`, an optimization for indexing fields
|
|
NOOMV_INSTR_GETFIELD,
|
|
// pops (table, value), sets `table[consts[op.uD]] = value`, an optimization for changing fields
|
|
NOOMV_INSTR_SETFIELD,
|
|
// pushes `(*upvals[op.A])[consts[op.uD]]`
|
|
NOOMV_INSTR_GETUPFIELD,
|
|
// pops [value], sets `(*upvals[op.A])[consts[op.uD]] = value`
|
|
NOOMV_ISNTR_SETUPFIELD,
|
|
|
|
// does a unary or binary operation.
|
|
// if op.a == 1, its a unary operation.
|
|
// if op.a == 2, its a binary operation.
|
|
// op.b stores the operation type.
|
|
NOOMV_INSTR_OP,
|
|
|
|
// pops value, sets stack[op.uD] = value
|
|
NOOMV_INSTR_SETVAL,
|
|
// pops value, sets *upvals[op.uD] = value
|
|
NOOMV_INSTR_SETUPVAL,
|
|
// pops value, sets _G[consts[op.uD]] = value
|
|
NOOMV_INSTR_SETGLOBAL,
|
|
|
|
// Control flow
|
|
|
|
// 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.
|
|
NOOMV_INSTR_JMP,
|
|
// pops value, if value is truthy, does the same as JMP.
|
|
NOOMV_INSTR_CJMP,
|
|
// pops value, if value is falsy, does the same as JMP.
|
|
NOOMV_INSTR_CNJMP,
|
|
|
|
// 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,
|
|
// rotate the the top op.a items by op.sD
|
|
NOOMV_INSTR_ROTATE,
|
|
// 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,
|
|
|
|
// pop and concatenate the top [op.uD+1] values to a string and push it
|
|
NOOMV_INSTR_CONCAT,
|
|
|
|
// mark stack slot op.uD as to-be-closed
|
|
NOOMV_INSTR_CLOSE,
|
|
|
|
NOOMV_INSTR_NOP2 = 0xff,
|
|
} noomV_Opcode;
|
|
|
|
typedef struct noomV_DisInfo {
|
|
const char *name;
|
|
enum {
|
|
NOOMV_DIS_NONE,
|
|
NOOMV_DIS_BC,
|
|
NOOMV_DIS_uD,
|
|
NOOMV_DIS_sD,
|
|
} arg;
|
|
} noomV_DisInfo;
|
|
|
|
extern noomV_DisInfo noomV_disInfo[NOOMV_INSTR_NOP2];
|
|
|
|
typedef struct noomV_Inst {
|
|
noomV_Opcode op;
|
|
unsigned char a;
|
|
union {
|
|
struct {
|
|
unsigned char b;
|
|
unsigned char c;
|
|
};
|
|
short ss;
|
|
unsigned short us;
|
|
};
|
|
} noomV_Inst;
|
|
|
|
typedef struct noomV_UpvalDesc {
|
|
char* name;
|
|
unsigned char idx;
|
|
// whether the index is a stack index
|
|
noom_bool_t isStack;
|
|
} noomV_UpvalDesc;
|
|
|
|
typedef struct noomV_LocalDesc {
|
|
char* name;
|
|
unsigned char stackIdx;
|
|
// to forbid changing it with debug.setlocal
|
|
noom_bool_t isConst;
|
|
// offset of first instruction where local exists
|
|
unsigned int pcStart;
|
|
// offset of first instruction where local is dropped
|
|
unsigned int pcEnd;
|
|
} noomV_LocalDesc;
|
|
|
|
typedef struct noomV_Function {
|
|
noomV_Object obj;
|
|
// source location string
|
|
noomV_String* chunkname;
|
|
// globals. Lua 5.1 only, use the _ENV upvalue for Lua 5.2+
|
|
noomV_Table* env;
|
|
noomV_Inst* code;
|
|
noomV_Value* consts;
|
|
struct noomV_Function** protos;
|
|
noomV_UpvalDesc* upvals;
|
|
noomV_LocalDesc* locals;
|
|
unsigned int codesize;
|
|
// line of function
|
|
unsigned int linedefined;
|
|
// line of end
|
|
unsigned int lastlinedefined;
|
|
// very size limited
|
|
// ░░░░░░░░░ ░ ▒░░▒ ▒░ ░░ ░
|
|
// ░░░░░░░░░ █░█░▓▓ ░█▒████▒░ █░░████▓
|
|
// ░░░░░░░░░ ▓████████░█░▒████▓░████ ░█░
|
|
// ░░░░░░░░░ ░░░ ░█▓░█▒█░█░▒▒█▓████▒ ░█▒█ ░█░░
|
|
// ░░░░░░░░░ ░░░░░ ░█▓▓███▒█ ▓▓▒▒▒▓▓▒ ▒█▒█▓██▓▓
|
|
// ░░░░░░░░░ ░░ ░▓▓███▓█▒░░█▒▓█▓██▓ ▒▓█▓▓▓█▒▒
|
|
// ░░░░░░░░░░ ░░ ▒▓█░█▒█░ ░▓▓██████░░██░░▒█
|
|
// ░░░░░░░░░░ ░░░░ ░░ ▓▓█▒█▓█▒░ ▒▒▒█▒░█░ ███░░█
|
|
// ░░░░░░░░░░ ░░ ▓▒▓███░█▒ ▒░░░▓▓█░░██░▓▒██
|
|
// ░░░░░░░░░░ ░░ ░ ░░ ░░ ░░ ░░ ▒░ ░░░ ░░
|
|
// ░░░░░░░░░░ ░ ░░░ ░ ░ ░ ░
|
|
// ░░░░░░░░░░ ░░░░ ░░ ░
|
|
// ░ ░░░░░░░░░ ░░░░ ░ ░ ░ ░░ ░░░ ░░░ ░ ░░
|
|
// ░░░░░░░░░ ░ █████ ▒████ ░▓███▓░ ░████░ ░████▓ ░████░ ░▓███▓ ░████░
|
|
// ░░ ░░░░░░░░░ █▓▒██░░██▓██▒░░██▒██░░██▒▓██░▒██▒██ ██▓▓██ ░██▒██░ ██▒▓██
|
|
// ░░ ░░░░░░░░░░ ██░▒█▓ ░██░██▒ ▒█▒░██ ░██ ██░ ▓█▒░██ ░██░██░ ▓█▒░██ ██░
|
|
// ░░░░░░░░░░░ ░▓███░▒█▓ ██░██░ ▒██▒██ ░██░██ ▒█▒░██ ░██░██░ ▒█▓▒█▓ ░██░
|
|
// ░░░░░░░░░░ ░ ░▒███░ ▓████░▓█▒ ░██░██░ ▒██▒█▓ ░██░██ ░▒█▒░██ ░██░██░ ░██▒█▓ ░██░░
|
|
// ░░░░░░░░░ ░░ ░ ░░██▓▓█▓░░██ ██░ ▒█▓▒█▓ ██░██░ ▒█▒░██ ░██░██░ ░██▒█▓ ░██░░
|
|
// ░░░░░░░░░░ ░░░░ ░ ░███▒██ ░██ ██▒ ▓█▒▒██ ░██░██░ ██░░██░░██ ██▒ ▓█░░██ ░██
|
|
// ░░░░░░░░░ ░░░ ░█████▒░█████▒▒▓█████ ░█████▒ ▒█████▓██████▓ ▒██▓██░ █████▒
|
|
// ░░░░░░░░░ ░░ ░████▒ ░░███▒░█▒▒███░ ░███▓ ▓███░▓█░███▓░ ▒███░ ░███▒
|
|
// ░░░░░░░░░░ ░█░ ░ ░ ▒▒ ░
|
|
// ░░░░░░░░░ ░ ░
|
|
// ░░░░░░░░░ ░░░░
|
|
// ░░░░░░░░░ ░░░░░ ░░ ░ ░ ░ ░
|
|
// ░░░░░░░░░ ░░ ░░▒░░ ░▓▒ ░█▓ ░ ░ ░
|
|
// ░░░░░░░░░ ░░ ▒█████░ ░██ ░██ ░ ░
|
|
// ░░░░░░░░░░░░░ ██▒▒███ ░░ ░ ░ ░░ ░░ ░██
|
|
// ░ ░░░░░░░░░░░░░░░ ██░ ░▓▓ ░████ ░████░░█▓ ▒███▓░░██
|
|
// ░░░░░░░░░░░░░░░░░░░░░▓███░░ ░██████░██▓██▓▒██░██▓██▒▒██░
|
|
// ░░░░░░░░░░░░░░░░░░░░ ░████░░█▓ ██░██ ▓▓░██ ░░▒██▓▒██░ ░
|
|
// ░░░░░░░░░░░░░░░░░░░ ░░░░███▒█▓ ██▒██ ░░██░█████▓▒██░ ░░ ░
|
|
// ░░░░░░░░░░░░░░░░░░ ░██▒ ░██▒██░░██░██ ░█▓░██░██░▓█▓░██░ ░ ██▓████████▓▒██▒
|
|
// ░░░░░░░░░░░░░░░░░ ░██████▓ █████▓ █████▓▒██░█████▓░██░ ░ ░░░███████████▓█▓
|
|
// ░░░░░░░░░░░░░░░░░░ ░▓███▒ ░███▒ ░▓██▓ ░█▓ ▒██▓█▓░██░ ░████████████▓█░
|
|
// ░░░░░░░░░░░░░░░░░ ░ ░ ░█████████▓██
|
|
// ░░░░░░░░░░░░░░ ░░ ▒███████▓█▓
|
|
// ░ ░░░░░░░░░░░░░░ ░░░░ ██▓██▓▓▓███ ░
|
|
// ░░░░░░░░░░░░░░░ ░░ ░ ░███████████
|
|
// ░░░░░░░░░░░░ ░░ ░ ████████████ ▒
|
|
// ░░░░░░░░░░░░ ░░▒▒░░ ░ ░██░▓█░ ░ ░░████ ▒██████ █
|
|
// ░░░░░░░░░░░ ▓█████░ ░██ ██░░█▓░ ▓████ ▓█████ ░█ ▒▒
|
|
// ░░░░░░░░░░░ ▒██░░██▒░░░░░ ░░░░░ ░░░░██ ░░░██░ █████ ▒█████ ▒█ ▒█░
|
|
// ░░░░░░░░░░░░░ ██▒ ░█▓░████░████░ ░█████ ██▒████ █████ ▓████▒ ████░
|
|
// ░░░░░░░░░░░░ ██░ ▒███▓██▓▓██ ██▒▒██ ██░▒██░ ████▒ █████ ▒████████
|
|
// ░░░░░░░░░░ ██░ ░ ▒██░ ██▒▒██░██ ░██ ██ ░██░ ░████ ░█████ ▓██▓▒░░
|
|
// ░░░░░░░░░░ ██▒ ░▒░▒██ ░██████▒██ ░██░██░░██ ▒███▒ █████▒
|
|
// ░ ░░░░░░░░░ ▓█▓░ ▓█▓▒██ ░██░ ░░░██ ░██ ██░░██░ ░███░░░█████
|
|
// ░░░░░░░░░ ██████ ▒█▓ ▓█████ ██████ ██░░███░ ░██▒ █████▒
|
|
// ░░░░░░░░░ ░▓███░░░█▓ ░░▒███░░░██▓█▓ ▓█░ ▒█▓░ ▓▓ ░█████
|
|
// ░░░░░░░░░ ░░░░ ░░ ░░ ░ ░ ▓▒ ▓████░
|
|
// ░░░░░░░░ ░░ ░░ ░ ▒▓ ████▒
|
|
// ░ ░░░░░ ░ ░ ▒░▓███▒
|
|
// ░░░░ ░ ░▓▓██
|
|
// ░░░
|
|
// ░░░
|
|
// ░░
|
|
unsigned char argc;
|
|
unsigned char flags;
|
|
unsigned short constsize;
|
|
unsigned char protosize;
|
|
unsigned char upvalsize;
|
|
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
|
|
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;
|
|
} lua;
|
|
struct {
|
|
noom_KFunction* resumeFunc;
|
|
void* resumeCtx;
|
|
} c;
|
|
};
|
|
} noomV_CallFrame;
|
|
|
|
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;
|
|
struct noomV_Thread* resumedBy;
|
|
struct noomV_Thread* resuming;
|
|
} noomV_Thread;
|
|
|
|
struct noom_LuaVM {
|
|
noomV_Object* heap;
|
|
noomV_Object* graySet;
|
|
noomV_Value oomVal;
|
|
noomV_Table* globals;
|
|
noomV_Table* registry;
|
|
noomV_Thread* mainThread;
|
|
noomV_Thread* currentThread;
|
|
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
|