sbBuffer_initialize(&g_bigint_blocks, sizeof(intblk*));
}
-hInteger sbInteger_new(u64 value) {
+hInteger sbInteger_new(i64 value) {
if (value >= SARABANDE_INT_MIN && value <= SARABANDE_INT_MAX) {
return value;
} else {
void sbInteger_sys_init();
-hInteger sbInteger_new(u64 value);
+hInteger sbInteger_new(i64 value);
hInteger sbInteger_sum(hInteger a, hInteger b);
};
}
-flag sbV_eq(hV *a, hV *b) {
+flag sbV_eq(const hV *a, const hV *b) {
if (a->type != b->type) return FALSE;
if (a->type == ITX_TOMBSTONE || b->type == ITX_TOMBSTONE) return FALSE;
if (a->type == IT_NOTHING || b->type == IT_NOTHING) return FALSE;
}
}
-void sbV_retain(hV *a) {
+void sbV_retain(const hV *a) {
/* for some classes (e.g. nil, float), we don't need to retain them at all
* because they are trivially copiable. some classes (integer, string) need
* to sometimes be retained but sometimes not, so we delegate to them to
}
}
-void sbV_release(hV *a) {
+void sbV_release(const hV *a) {
if (a->type == IT_STRING) {
sbString_release(a->string);
}
#include "common.h"
+#define HVINT(n) ((hV) { .type = IT_INTEGER, .integer = n })
+#define HVSTR(s) ((hV) { .type = IT_STRING, .string = OBJSL(s) })
+
typedef u64 hHash;
typedef u64 hString;
typedef u64 hSymbol;
hV sbV_hash(hHash hash);
hV sbV_int(hInteger i);
-flag sbV_eq(hV *a, hV *b);
+flag sbV_eq(const hV *a, const hV *b);
-void sbV_retain(hV *a);
-void sbV_release(hV *a);
+void sbV_retain(const hV *a);
+void sbV_release(const hV *a);
#endif
#include "data/string.h"
#include "data/hashtable.h"
#include "data/symbol.h"
+#include "vm/exec.h"
int main(int argc, char **argv) {
if (argc == 2) {
} else {
fprintf(stderr, "please provide a file as input\n");
}
+
+ sbVm vm = {0};
+ sbVm_initialize(&vm, 1048576, 1048576);
+
+ sbVmProgram pm;
+ sbVmProgram_initialize(&pm, 65536);
+
+ sbVmPartialBlock pb = sbVmBlock_create(4096, 4096);
+
+ u8 code[] = {
+ BC_LD_CONST, 0x00,
+ BC_LD_CONST, 0x01,
+ BC_OP_ADD,
+ BC_HALT,
+ };
+
+ sbVmBlock_write_code(&pb, code, sizeof(code));
+
+ sbVmBlock_add_constant(&pb, &HVINT(5));
+ sbVmBlock_add_constant(&pb, &HVINT(11));
+
+ sbVmProgram_add_block(&pm, &pb);
+
+ sbVm_execute(&vm, &pm);
+
+ printf("Stack result: ");
+ for (u8 *p = vm.stack; p < vm.sp; p++) {
+ printf("%02X ", *p);
+ }
+ printf("\n");
+
+ sbVmBlock_deinitialize(&pb);
}
if (expand_size == 0) return NULL;
if (buf->capacity == 0 || buf->data == NULL) {
- fprintf(stderr, "cannot expand invalid buffer!\n");
- return NULL;
+ PANIC("cannot expand invalid buffer!");
}
char *result;
buf->capacity = new_capacity;
buf->data = new_data;
} else {
- fprintf(stderr, "failed to expand buffer!");
- return NULL;
+ PANIC("failed to expand buffer!");
}
}
}
}
-void sbBuffer_append(hBuffer buf, const void *data, usize data_length) {
- if (data_length == 0) return;
+void *sbBuffer_append(hBuffer buf, const void *data, usize data_length) {
+ if (data_length == 0) return NULL;
char *put = sbBuffer_expand(buf, data_length);
memcpy(put, data, data_length);
+
+ return put;
}
void sbBuffer_reset(hBuffer buf) {
void sbBuffer_set_size(hBuffer buf, usize new_size);
-void sbBuffer_append(hBuffer buf, const void *data, usize data_length);
+void *sbBuffer_append(hBuffer buf, const void *data, usize data_length);
void sbBuffer_reset(hBuffer buf);
#define INITIAL_PROGRAM_BLOCK_SIZE 32
-void sbVmProgram_init(sbVmProgram *pm, usize initial_arena_size) {
- *pm = (sbVmProgram) {0};
- sbArena_initialize(&pm->arena, initial_arena_size);
-}
-
sbVmPartialBlock sbVmBlock_create(usize initial_bytecode_size, usize initial_constant_size) {
sbVmPartialBlock pb = {0};
sbBuffer_initialize(&pb.bytecode, initial_bytecode_size);
sbBuffer_deinitialize(&pb->constants);
}
-void sbVmProgram_initialize(sbVmProgram *pm) {
- pm->block_count = 0;
+void sbVmBlock_write_code(sbVmPartialBlock *pb, const u8 *data, usize length) {
+ sbBuffer_append(&pb->bytecode, data, length);
+}
+
+void sbVmBlock_add_constant(sbVmPartialBlock *pb, hV *constant) {
+ sbV_retain(constant);
+
+ sbBuffer_append(&pb->constants, constant, sizeof(hV));
+}
+
+void sbVmProgram_initialize(sbVmProgram *pm, usize initial_arena_size) {
+ *pm = (sbVmProgram) {0};
+ sbArena_initialize(&pm->arena, initial_arena_size);
pm->block_capacity = INITIAL_PROGRAM_BLOCK_SIZE;
pm->blocks = malloc(pm->block_capacity * sizeof(sbVmBlock));
}
void sbVmProgram_deinitialize(sbVmProgram *pm) {
free(pm->blocks);
+ sbArena_deinitialize(&pm->arena);
*pm = (sbVmProgram) {0};
}
+/* finalize a partial-block and add it to the program.
+ * copies the data, so the same partial-block object can be reset
+ * and reused after calling this function. */
sbBlockId sbVmProgram_add_block(sbVmProgram *pm, sbVmPartialBlock *pb) {
- const usize bytecode_length = pb->bytecode.size;
- char *bytecode = sbArena_alloc(&pm->arena, bytecode_length);
+ usize bytecode_length = pb->bytecode.size;
+ usize constants_length = pb->constants.size;
+ while (bytecode_length % 8 != 0) bytecode_length ++;
+
+ u8 *block_data = sbArena_alloc(&pm->arena, bytecode_length + constants_length);
+
+ u8 *bytecode = &block_data[0];
memcpy(bytecode, pb->bytecode.data, bytecode_length);
- const usize constants_length = pb->constants.size;
- char *constants = sbArena_alloc(&pm->arena, constants_length);
+ u8 *constants = &block_data[bytecode_length];
memcpy(constants, pb->constants.data, constants_length);
sbVmBlock bk = {
#include "common.h"
+#define TEST_VM_BLK(bc, cn) ((sbVmBlock) { \
+ .bytecode = bc, \
+ .bytecode_length = sizeof(bc), \
+ .constants = cn, \
+ .constants_count = sizeof(cn)/sizeof(hV) \
+ })
+
/* dynamic sized block that we can more easily add
* things to while compiling */
typedef struct sbVmPartialBlock {
/* fixed size / frozen block of bytecode */
typedef struct sbVmBlock {
- const char *bytecode;
+ const u8 *bytecode;
usize bytecode_length;
const hV *constants;
usize constants_count;
typedef usize sbBlockId;
-void sbVmProgram_init(sbVmProgram *pm, usize initial_arena_size);
-
sbVmPartialBlock sbVmBlock_create(usize initial_bytecode_size, usize initial_constant_size);
void sbVmBlock_reset(sbVmPartialBlock *pb);
void sbVmBlock_deinitialize(sbVmPartialBlock *pb);
-void sbVmProgram_initialize(sbVmProgram *pm);
+void sbVmBlock_write_code(sbVmPartialBlock *pb, const u8 *data, usize length);
+
+void sbVmBlock_add_constant(sbVmPartialBlock *pb, hV *constant);
+
+void sbVmProgram_initialize(sbVmProgram *pm, usize initial_arena_size);
void sbVmProgram_deinitialize(sbVmProgram *pm);
+/* finalize a partial-block and add it to the program.
+ * copies the data, so the same partial-block object can be reset
+ * and reused after calling this function. */
sbBlockId sbVmProgram_add_block(sbVmProgram *pm, sbVmPartialBlock *pb);
typedef enum sbOpcode {
BC_NOP = 0,
- BC_LD_CONST,
- BC_LD_IMM,
- BC_LD_VAR,
- BC_LD_CTX,
- BC_LD_UPVAL,
- BC_ST_VAR,
- BC_ST_UPVAL,
- BC_CALL,
- BC_JMP,
- BC_RET = 10,
- BC_SEND,
+ BC_LD_IMM, /* push immediate value to stack */
+ BC_LD_CONST, /* push constants[index] to stack */
+ BC_LD_CTX, /* look up key in context object and push result */
+ BC_LD_VAR, /* push value onto stack from variable */
+ BC_LD_UPVAL, /* push value onto stack from closure */
+ BC_ST_VAR, /* pop value from stack into variable */
+ BC_ST_UPVAL, /* pop value from stack into closure */
+ BC_POP, /* pop value from stack */
+ BC_CALL, /* push return address and jump to new block */
+ BC_JMP, /* local jump inside current block */
+ BC_RET, /* return to calling block */
+ BC_SEND, /* like call, but jump to object method (maybe this is the same as call?) */
BC_OP_ADD,
BC_OP_SUB,
BC_OP_MUL,
- BC_OP_DIV,
+ BC_OP_DIV = 0x10,
+ BC_OP_FLDIV,
BC_OP_NEG,
BC_OP_MOD,
BC_OP_POW,
BC_OP_DEREF,
- BC_LIST_NEW = 20,
+ BC_ALLOC_VARS,
+ BC_LIST_NEW,
BC_HASH_NEW,
BC_LIST_ADD,
BC_HASH_ADD,
#include "vm/exec.h"
+
+#include "data/integer.h"
+
+void call_block(hVm vm, usize block_id);
+void execute_instruction(hVm vm);
+
+void sbVm_initialize(hVm vm, usize stacksize, usize rstacksize) {
+ *vm = (sbVm) {0};
+ vm->stack = malloc(stacksize);
+ vm->stacksize = stacksize;
+ vm->rstack = malloc(rstacksize);
+ vm->rstacksize = rstacksize;
+
+ vm->sp = vm->stack;
+ vm->fp = (sbVmStackFrame*)vm->rstack;
+ vm->rp = vm->rstack;
+}
+
+void sbVm_deinitialize(hVm vm) {
+ free(vm->stack);
+ free(vm->rstack);
+ *vm = (sbVm) {0};
+}
+
+sbVmStatus sbVm_execute(hVm vm, sbVmProgram *pm) {
+ vm->program = pm;
+ vm->running = TRUE;
+
+ /* Let's start at the very beginning.
+ * A very good place to start. */
+ call_block(vm, 0);
+
+ while (vm->running) {
+ execute_instruction(vm);
+ }
+
+ return VM_STAT_SUCCESS;
+}
+
+/* --- */
+
+void call_block(hVm vm, usize block_id) {
+ sbVmBlock *blk = &vm->program->blocks[block_id];
+
+ sbVmStackFrame frame = {
+ .return_addr = vm->ip,
+ .last_fp = vm->fp,
+ .last_rp = vm->rp,
+ .block = blk,
+ };
+ *(sbVmStackFrame*)vm->rp = frame;
+ vm->fp = (sbVmStackFrame*)vm->rp;
+ vm->rp += sizeof(sbVmStackFrame);
+ vm->ip = &blk->bytecode[0];
+}
+
+void return_from_block(hVm vm) {
+ sbVmStackFrame *frame = (sbVmStackFrame*)vm->fp;
+
+ vm->fp = frame->last_fp;
+ vm->rp = frame->last_rp;
+ vm->ip = frame->return_addr;
+}
+
+void push_stack(hVm vm, const hV *value) {
+ sbV_retain(value);
+ *(hV*)vm->sp = *value;
+ vm->sp += sizeof(hV);
+}
+
+hV *pop_stack(hVm vm) {
+ vm->sp -= sizeof(hV);
+ sbV_release((hV*)vm->sp);
+ return (hV*)vm->sp;
+}
+
+sbOpcode get_opcode(hVm vm) {
+ return *(vm->ip++);
+}
+
+/* small numbers that are parameters to opcodes can
+ * just be encoded directly as 1 byte. numbers of 16-bit
+ * scale can be encoded as FE + byte + byte, numbers of
+ * 32 bit scale can be FF + byte + byte + byte + byte,
+ * in big-endian format */
+u32 get_param(hVm vm) {
+ u32 result = *((u8*)vm->ip++);
+
+ if (result == BC_LONG_NUM) {
+ result = *(vm->ip++);
+ result <<= 8;
+ result |= *(vm->ip++);
+ } else if (result == BC_VLONG_NUM) {
+ result = *(vm->ip++);
+ result <<= 8;
+ result |= *(vm->ip++);
+ result <<= 8;
+ result |= *(vm->ip++);
+ result <<= 8;
+ result |= *(vm->ip++);
+ }
+
+ return result;
+}
+
+/* execute one instruction! wow! */
+void execute_instruction(hVm vm) {
+ sbOpcode op = get_opcode(vm);
+ u32 param;
+ hV *v, a, b, c;
+
+ switch (op) {
+ case BC_NOP:
+ return;
+ case BC_HALT:
+ vm->running = FALSE;
+ return;
+ case BC_LD_IMM:
+ param = get_param(vm);
+ push_stack(vm, &HVINT(param));
+ return;
+ case BC_LD_CONST:
+ param = get_param(vm);
+ push_stack(vm, &vm->fp->block->constants[param]);
+ return;
+ case BC_LD_CTX:
+ PANIC("todo");
+ case BC_LD_VAR:
+ param = get_param(vm);
+ push_stack(vm, &vm->fp->locals[param]);
+ return;
+ case BC_LD_UPVAL:
+ /* Hey, was there upval in there? */
+ PANIC("todo");
+ case BC_ST_VAR:
+ param = get_param(vm);
+ vm->fp->locals[param] = *pop_stack(vm);
+ return;
+ case BC_ST_UPVAL:
+ PANIC("todo");
+ case BC_POP:
+ v = pop_stack(vm);
+ sbV_release(v);
+ return;
+ case BC_CALL:
+ param = get_param(vm);
+ call_block(vm, param);
+ return;
+ case BC_JMP:
+ param = get_param(vm);
+ vm->ip = &vm->fp->block->bytecode[param];
+ return;
+ case BC_RET:
+ return_from_block(vm);
+ return;
+ case BC_SEND:
+ PANIC("todo");
+ case BC_OP_ADD:
+ b = *pop_stack(vm);
+ a = *pop_stack(vm);
+ if (a.type == IT_INTEGER && b.type == IT_INTEGER) {
+ c = sbV_int(sbInteger_sum(a.integer, b.integer));
+ push_stack(vm, &c);
+ } else {
+ PANIC("todo");
+ }
+ return;
+ case BC_OP_SUB:
+ b = *pop_stack(vm);
+ a = *pop_stack(vm);
+ if (a.type == IT_INTEGER && b.type == IT_INTEGER) {
+ c = sbV_int(sbInteger_diff(a.integer, b.integer));
+ push_stack(vm, &c);
+ } else {
+ PANIC("todo");
+ }
+ return;
+ case BC_OP_MUL:
+ b = *pop_stack(vm);
+ a = *pop_stack(vm);
+ if (a.type == IT_INTEGER && b.type == IT_INTEGER) {
+ c = sbV_int(sbInteger_mul(a.integer, b.integer));
+ push_stack(vm, &c);
+ } else {
+ PANIC("todo");
+ }
+ return;
+ case BC_OP_DIV:
+ PANIC("todo");
+ case BC_OP_FLDIV:
+ b = *pop_stack(vm);
+ a = *pop_stack(vm);
+ if (a.type == IT_INTEGER && b.type == IT_INTEGER) {
+ c = sbV_int(sbInteger_floordiv(a.integer, b.integer));
+ push_stack(vm, &c);
+ } else {
+ PANIC("todo");
+ }
+ return;
+ case BC_OP_NEG:
+ case BC_OP_MOD:
+ case BC_OP_POW:
+ case BC_OP_DEREF:
+ PANIC("todo");
+ case BC_ALLOC_VARS:
+ param = get_param(vm);
+ vm->rp += param * sizeof(hV);
+ return;
+ case BC_LIST_NEW:
+ case BC_HASH_NEW:
+ case BC_LIST_ADD:
+ case BC_HASH_ADD:
+ PANIC("todo");
+ case BC_LONG_NUM:
+ case BC_VLONG_NUM:
+ PANIC("illegal opcode $%02X at position $%08zX\n", op, (usize)vm->ip);
+ default:
+ PANIC("unrecognized opcode $%02X at position $%08zX\n", op, (usize)vm->ip);
+ }
+}
#include "common.h"
+
+#include "vm/block.h"
+
+/* OK so, we have two stacks (because iirc, you kind of need two stacks
+ * to make a stack machine work.) It's a little bit like FORTH, in that
+ * we have one "normal" stack which is used by most instructions (we
+ * can push values onto the stack, branch based on these values, etc.)
+ * and then one extra stack which tracks the function call structure and
+ * can be manipulated using special instructions. */
+
+/* We also have the "rstack". When we call a function, a stack frame is
+ * pushed onto the rstack, which saves the return address, the last
+ * value of fp and rp, and a pointer to the current code block (so we can
+ * find things like constants.) then, *above* this on the stack, we can
+ * use the space to store local variables, using instructions to move
+ * those slots to/from the main stack by index. when we return, we reset
+ * the fp and rp to point to the calling function's stack frame instead. */
+
+/* (we need to track fp so we can remember where the return address is,
+ * and we need to track rp so we know where to put the *next* frame.
+ * this then lets us leave function arguments and function return values
+ * on the normal stack, without worrying about needing to shuffle
+ * things around.) */
+
+/* I guess really the rstack is kind of like the "normal" C call stack,
+ * in that it's where we put "stack-allocated" local variables and save
+ * the stack frame, and the "normal" stack here does things that would
+ * normally be accomplished using registers in a non-stack-machine
+ * system... but, ah well. */
+
+/* Calling convention for function arguments + return values is to put
+ * a number at the top of the stack representing the number of arguments
+ * or values returned, followed by the values in order from top to
+ * bottom. */
+
+/* Also these stacks grow upwards in memory, from low to high addresses. */
+
+typedef enum sbVmStatus {
+ VM_STAT_UNKNOWN,
+ VM_STAT_SUCCESS,
+ VM_STAT_FAILURE,
+} sbVmStatus;
+
+typedef struct sbVmStackFrame {
+ const u8 *return_addr;
+ struct sbVmStackFrame *last_fp;
+ u8 *last_rp;
+ const sbVmBlock *block;
+ hV locals[];
+} sbVmStackFrame;
+
+typedef struct sbVm {
+ u8 *stack; /* for calculations */
+ u8 *rstack; /* for locals and return addresses, like FORTH */
+
+ const u8 *ip; /* instruction pointer */
+ sbVmStackFrame *fp; /* frame pointer */
+ u8 *sp; /* stack pointer */
+ u8 *rp; /* rstack pointer */
+
+ usize stacksize; /* to detect overflow, save these */
+ usize rstacksize; /* ^^ */
+
+ sbVmProgram *program;
+ flag running;
+} sbVm;
+
+typedef sbVm *hVm;
+
+void sbVm_initialize(hVm vm, usize stacksize, usize rstacksize);
+
+void sbVm_deinitialize(hVm vm);
+
+sbVmStatus sbVm_execute(hVm vm, sbVmProgram *pm);