1
0
mirror of https://github.com/janet-lang/janet synced 2024-12-25 07:50:27 +00:00

Remove longjump/setjump from vm loop. Add out of memory

behavior option.
This commit is contained in:
Calvin Rose 2017-03-08 15:08:46 -05:00
parent 68f834f03b
commit 69624495ec
12 changed files with 215 additions and 181 deletions

View File

@ -6,7 +6,7 @@ TARGET=interp
PREFIX=/usr/local
# C sources
HEADERS=vm.h ds.h compile.h parse.h value.h datatypes.h gc.h util.h
HEADERS=vm.h ds.h compile.h parse.h value.h datatypes.h gc.h util.h gst.h
SOURCES=main.c parse.c value.c vm.c ds.c compile.c gc.c
OBJECTS=$(patsubst %.c,%.o,$(SOURCES))

View File

@ -2,6 +2,20 @@
#define COMPILE_H_9VXF71HY
#include "datatypes.h"
#include <setjmp.h>
typedef struct GstCompiler GstCompiler;
typedef struct GstScope GstScope;
/* Compilation state */
struct GstCompiler {
Gst *vm;
const char *error;
jmp_buf onError;
GstScope *tail;
GstArray *env;
GstBuffer *buffer;
};
/* Initialize the Compiler */
void gst_compiler(GstCompiler *c, Gst *vm);
@ -15,10 +29,4 @@ void gst_compiler_add_global_cfunction(GstCompiler *c, const char *name, GstCFun
/* Compile a function that evaluates the given form. */
GstFunction *gst_compiler_compile(GstCompiler *c, GstValue form);
/* Macro expansion. Macro expansion happens prior to the compilation process
* and is completely separate. This allows the compilation to not have to worry
* about garbage collection and other issues that would complicate both the
* runtime and the compilation. */
int gst_macro_expand(Gst *vm, GstValue x, GstObject *macros, GstValue *out);
#endif /* end of include guard: COMPILE_H_9VXF71HY */

View File

@ -2,7 +2,6 @@
#define DATATYPES_H_PJJ035NT
#include <stdint.h>
#include <setjmp.h>
/* Flag for immutability in an otherwise mutable datastructure */
#define GST_IMMUTABLE 1
@ -36,19 +35,15 @@ typedef struct GstArray GstArray;
typedef struct GstBuffer GstBuffer;
typedef struct GstObject GstObject;
typedef struct GstThread GstThread;
typedef GstValue (*GstCFunction)(Gst * vm);
typedef int (*GstCFunction)(Gst * vm);
/* Implementation details */
typedef struct GstParser GstParser;
typedef struct GstCompiler GstCompiler;
typedef struct GstFuncDef GstFuncDef;
typedef struct GstFuncEnv GstFuncEnv;
/* Definitely implementation details */
typedef struct GstStackFrame GstStackFrame;
typedef struct GstParseState GstParseState;
typedef struct GstBucket GstBucket;
typedef struct GstScope GstScope;
/* The general gst value type. Contains a large union and
* the type information of the value */
@ -155,6 +150,11 @@ struct GstStackFrame {
uint16_t *pc;
};
/* VM return status from c function */
#define GST_RETURN_OK 0
#define GST_RETURN_ERROR 1
#define GST_RETURN_CRASH 2
/* The VM state */
struct Gst {
/* Garbage collection */
@ -166,47 +166,9 @@ struct Gst {
GstThread *thread;
/* Return state */
const char *crash;
jmp_buf jump;
GstValue error;
GstValue ret; /* Returned value from VMStart. Also holds errors. */
GstValue ret; /* Returned value from gst_start. Also holds errors. */
};
struct GstParser {
Gst *vm;
const char *error;
GstParseState *data;
GstValue value;
uint32_t count;
uint32_t cap;
uint32_t index;
uint32_t flags;
enum {
GST_PARSER_PENDING = 0,
GST_PARSER_FULL,
GST_PARSER_ERROR
} status;
};
/* Compilation state */
struct GstCompiler {
Gst *vm;
const char *error;
jmp_buf onError;
GstScope *tail;
GstArray *env;
GstBuffer *buffer;
};
/* String utils */
#define gst_string_raw(s) ((uint32_t *)(s) - 2)
#define gst_string_length(v) (gst_string_raw(v)[0])
#define gst_string_hash(v) (gst_string_raw(v)[1])
/* Tuple utils */
#define gst_tuple_raw(s) ((uint32_t *)(s) - 2)
#define gst_tuple_length(v) (gst_tuple_raw(v)[0])
#define gst_tuple_hash(v) (gst_tuple_raw(v)[1])
/* Bytecode */
enum GstOpCode {
GST_OP_ADD = 0, /* Addition */

3
gc.c
View File

@ -184,7 +184,7 @@ void gst_sweep(Gst *vm) {
static void *gst_alloc_prepare(Gst *vm, char *rawBlock, uint32_t size) {
GCMemoryHeader *mdata;
if (rawBlock == NULL) {
gst_crash(vm, "out of memory");
GST_OUT_OF_MEMORY;
}
vm->nextCollection += size;
mdata = (GCMemoryHeader *)rawBlock;
@ -216,7 +216,6 @@ void gst_collect(Gst *vm) {
gst_mark(vm, &thread);
}
gst_mark(vm, &vm->ret);
gst_mark(vm, &vm->error);
gst_sweep(vm);
vm->nextCollection = 0;
}

12
gst.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef gst_h_INCLUDED
#define gst_h_INCLUDED
#include "util.h"
#include "datatypes.h"
#include "vm.h"
#include "parse.h"
#include "compile.h"
#include "value.h"
#endif // gst_h_INCLUDED

14
main.c
View File

@ -1,10 +1,6 @@
#include <stdlib.h>
#include <stdio.h>
#include "datatypes.h"
#include "vm.h"
#include "parse.h"
#include "compile.h"
#include "value.h"
#include "gst.h"
/* Simple printer for gst strings */
void string_put(FILE *out, uint8_t * string) {
@ -15,16 +11,14 @@ void string_put(FILE *out, uint8_t * string) {
}
/* Test c function */
GstValue print(Gst *vm) {
int print(Gst *vm) {
uint32_t j, count;
GstValue nil;
count = gst_count_args(vm);
for (j = 0; j < count; ++j) {
string_put(stdout, gst_to_string(vm, gst_arg(vm, j)));
fputc('\n', stdout);
}
nil.type = GST_NIL;
return nil;
return GST_RETURN_OK;
}
/* A simple repl for debugging */
@ -98,7 +92,7 @@ void debug_repl(FILE *in, FILE *out) {
fprintf(out, "VM crash: %s\n", vm.crash);
} else {
fprintf(out, "VM error: ");
string_put(out, gst_to_string(&vm, vm.error));
string_put(out, gst_to_string(&vm, vm.ret));
printf("\n");
}
}

20
parse.h
View File

@ -3,6 +3,26 @@
#include "datatypes.h"
typedef struct GstParser GstParser;
typedef struct GstParseState GstParseState;
/* Holds the parsing state */
struct GstParser {
Gst *vm;
const char *error;
GstParseState *data;
GstValue value;
uint32_t count;
uint32_t cap;
uint32_t index;
uint32_t flags;
enum {
GST_PARSER_PENDING = 0,
GST_PARSER_FULL,
GST_PARSER_ERROR
} status;
};
/* Some parser flags */
#define GST_PARSER_FLAG_INCOMMENT 1
#define GST_PARSER_FLAG_EXPECTING_COMMENT 2

25
util.h
View File

@ -1,6 +1,16 @@
#ifndef util_h_INCLUDED
#define util_h_INCLUDED
/* String utils */
#define gst_string_raw(s) ((uint32_t *)(s) - 2)
#define gst_string_length(v) (gst_string_raw(v)[0])
#define gst_string_hash(v) (gst_string_raw(v)[1])
/* Tuple utils */
#define gst_tuple_raw(s) ((uint32_t *)(s) - 2)
#define gst_tuple_length(v) (gst_tuple_raw(v)[0])
#define gst_tuple_hash(v) (gst_tuple_raw(v)[1])
/* Memcpy for moving memory */
#ifndef gst_memcpy
#include <string.h>
@ -36,5 +46,20 @@
#define NULL ((void *)0)
#endif
/* C function helpers */
/* Return in a c function */
#define gst_c_return(vm, x) (do { (vm)->ret = (x); return GST_RETURN_OK; } while (0))
/* Throw error from a c function */
#define gst_c_throw(vm, e) (do { (vm)->ret = (e); return GST_RETURN_ERROR; } while (0))
/* What to do when out of memory */
#ifndef GST_OUT_OF_MEMORY
#include <stdlib.h>
#include <stdio.h>
#define GST_OUT_OF_MEMORY do { printf("out of memory.\n"); exit(1); } while (0)
#endif
#endif // util_h_INCLUDED

57
value.c
View File

@ -388,75 +388,78 @@ static uint8_t to_byte(GstNumber raw) {
return (uint8_t) raw;
}
/* Get a value out af an associated data structure. Can throw VM error. */
GstValue gst_get(Gst *vm, GstValue ds, GstValue key) {
/* Get a value out af an associated data structure.
* Returns possible c error message, and NULL for no error. The
* useful return value is written to out on success */
const char *gst_get(GstValue ds, GstValue key, GstValue *out) {
int32_t index;
GstValue ret;
switch (ds.type) {
case GST_ARRAY:
gst_assert_type(vm, key, GST_NUMBER);
if (key.type != GST_NUMBER) return "expected numeric key";
index = to_index(key.data.number, ds.data.array->count);
if (index == -1) gst_error(vm, "invalid array access");
if (index == -1) return "invalid array access";
ret = ds.data.array->data[index];
break;
case GST_TUPLE:
gst_assert_type(vm, key, GST_NUMBER);
if (key.type != GST_NUMBER) return "expected numeric key";
index = to_index(key.data.number, gst_tuple_length(ds.data.tuple));
if (index < 0) gst_error(vm, "invalid tuple access");
if (index < 0) return "invalid tuple access";
ret = ds.data.tuple[index];
break;
case GST_BYTEBUFFER:
gst_assert_type(vm, key, GST_NUMBER);
if (key.type != GST_NUMBER) return "expected numeric key";
index = to_index(key.data.number, ds.data.buffer->count);
if (index == -1) gst_error(vm, "invalid buffer access");
if (index == -1) return "invalid buffer access";
ret.type = GST_NUMBER;
ret.data.number = ds.data.buffer->data[index];
break;
case GST_STRING:
gst_assert_type(vm, key, GST_NUMBER);
if (key.type != GST_NUMBER) return "expected numeric key";
index = to_index(key.data.number, gst_string_length(ds.data.string));
if (index == -1) gst_error(vm, "invalid string access");
if (index == -1) return "invalid string access";
ret.type = GST_NUMBER;
ret.data.number = ds.data.string[index];
break;
case GST_OBJECT:
return gst_object_get(ds.data.object, key);
ret = gst_object_get(ds.data.object, key);
break;
default:
gst_error(vm, "cannot get");
return "cannot get";
}
return ret;
*out = ret;
return NULL;
}
/* Set a value in an associative data structure. Can throw VM error. */
void gst_set(Gst *vm, GstValue ds, GstValue key, GstValue value) {
/* Set a value in an associative data structure. Returns possible
* error message, and NULL if no error. */
const char *gst_set(Gst *vm, GstValue ds, GstValue key, GstValue value) {
int32_t index;
switch (ds.type) {
case GST_ARRAY:
if (ds.data.array->flags & GST_IMMUTABLE)
goto immutable;
gst_assert_type(vm, key, GST_NUMBER);
return "cannot set immutable value";
if (key.type != GST_NUMBER) return "expected numeric key";
index = to_index(key.data.number, ds.data.array->count);
if (index == -1) gst_error(vm, "invalid array access");
if (index == -1) return "invalid array access";
ds.data.array->data[index] = value;
break;
case GST_BYTEBUFFER:
if (ds.data.buffer->flags & GST_IMMUTABLE)
goto immutable;
gst_assert_type(vm, key, GST_NUMBER);
gst_assert_type(vm, value, GST_NUMBER);
return "cannot set immutable value";
if (key.type != GST_NUMBER) return "expected numeric key";
if (value.type != GST_NUMBER) return "expected numeric value";
index = to_index(key.data.number, ds.data.buffer->count);
if (index == -1) gst_error(vm, "invalid buffer access");
if (index == -1) return "invalid buffer access";
ds.data.buffer->data[index] = to_byte(value.data.number);
break;
case GST_OBJECT:
if (ds.data.object->flags & GST_IMMUTABLE)
goto immutable;
return "cannot set immutable value";
gst_object_put(vm, ds.data.object, key, value);
break;
default:
gst_error(vm, "cannot set");
return "cannot set";
}
return;
immutable:
gst_error(vm, "cannot set immutable value");
return NULL;
}

View File

@ -15,10 +15,10 @@ int gst_compare(GstValue x, GstValue y);
int gst_equals(GstValue x, GstValue y);
/* Get a value from an associative gst object. Can throw errors. */
GstValue gst_get(Gst *vm, GstValue ds, GstValue key);
const char *gst_get(GstValue ds, GstValue key, GstValue *out);
/* Set a value in an associative gst object. Can throw errors. */
void gst_set(Gst *vm, GstValue ds, GstValue key, GstValue value);
const char *gst_set(Gst *vm, GstValue ds, GstValue key, GstValue value);
/* Load a c style string into a gst value (copies data) */
GstValue gst_load_cstring(Gst *vm, const char *string);

170
vm.c
View File

@ -1,23 +1,28 @@
#include "vm.h"
#include "util.h"
#include "value.h"
#include "ds.h"
#include "gc.h"
/* Macros for errors in the vm */
/* Exit from the VM normally */
#define gst_exit(vm, r) return ((vm)->ret = (r), GST_RETURN_OK)
/* Bail from the VM with an error string. */
#define gst_error(vm, e) return ((vm)->ret = gst_load_cstring((vm), (e)), GST_RETURN_ERROR)
/* Crash. Not catchable, unlike error. */
#define gst_crash(vm, e) return ((vm)->crash = (e), GST_RETURN_CRASH)
/* Error if the condition is false */
#define gst_assert(vm, cond, e) do {if (!(cond)){gst_error((vm), (e));}} while (0)
static const char GST_NO_UPVALUE[] = "no upvalue";
static const char GST_EXPECTED_FUNCTION[] = "expected function";
static const char GST_EXPECTED_NUMBER_ROP[] = "expected right operand to be number";
static const char GST_EXPECTED_NUMBER_LOP[] = "expected left operand to be number";
/* Get a literal */
static GstValue gst_vm_literal(Gst *vm, GstFunction *fn, uint16_t index) {
if (index > fn->def->literalsLen) {
gst_error(vm, GST_NO_UPVALUE);
}
return fn->def->literals[index];
}
/* Load a function into the VM. The function will be called with
* no arguments when run */
static void gst_load(Gst *vm, GstValue callee) {
@ -61,53 +66,18 @@ int gst_start(Gst *vm, GstValue func) {
GstStackFrame frame;
GstValue temp, v1, v2;
uint16_t *pc;
/* Load the callee */
gst_load(vm, func);
/* Intialize local state */
thread = *vm->thread;
stack = thread.data + thread.count;
frame = *((GstStackFrame *)(stack - GST_FRAME_SIZE));
pc = frame.pc;
/* Set jmp_buf to jump back to for return. */
{
int n;
if ((n = setjmp(vm->jump))) {
/* Good return */
if (n == 1) {
return 0;
} else if (n == 2) {
/* Error. */
while (!frame.errorJump) {
/* Check for closure */
if (frame.env) {
frame.env->thread = NULL;
frame.env->stackOffset = frame.size;
frame.env->values = gst_alloc(vm, sizeof(GstValue) * frame.size);
gst_memcpy(frame.env->values,
thread.data + thread.count,
frame.size * sizeof(GstValue));
}
stack -= frame.prevSize + GST_FRAME_SIZE;
if (stack <= thread.data) {
thread.count = 0;
break;
}
frame = *((GstStackFrame *)(stack - GST_FRAME_SIZE));
}
if (thread.count < GST_FRAME_SIZE)
return n;
/* Jump to the error location */
pc = frame.errorJump;
/* Set error */
stack[frame.errorSlot] = vm->error;
} else {
/* Crash. just return */
return n;
}
}
}
/* Main interpreter loop */
mainloop:
for (;;) {
switch (*pc) {
@ -290,10 +260,16 @@ int gst_start(Gst *vm, GstValue func) {
/* Call the function */
if (temp.type == GST_CFUNCTION) {
/* Save current state to vm thread */
int status;
*((GstStackFrame *)(stack - GST_FRAME_SIZE)) = frame;
*vm->thread = thread;
v2 = temp.data.cfunction(vm);
goto ret;
vm->ret.type = GST_NIL;
status = temp.data.cfunction(vm);
v1 = vm->ret;
if (status == GST_RETURN_OK)
goto ret;
else
goto vm_error;
} else {
for (; i < locals; ++i)
stack[i].type = GST_NIL;
@ -308,7 +284,9 @@ int gst_start(Gst *vm, GstValue func) {
case GST_OP_CST: /* Load constant value */
gst_assert(vm, frame.callee.type == GST_FUNCTION, GST_EXPECTED_FUNCTION);
stack[pc[1]] = gst_vm_literal(vm, frame.callee.data.function, pc[2]);
if (pc[2] > frame.callee.data.function->def->literalsLen)
gst_error(vm, GST_NO_UPVALUE);
stack[pc[1]] = frame.callee.data.function->def->literals[pc[2]];
pc += 3;
break;
@ -343,7 +321,9 @@ int gst_start(Gst *vm, GstValue func) {
frame.env->stackOffset = thread.count;
frame.env->values = NULL;
}
temp = gst_vm_literal(vm, frame.callee.data.function, pc[2]);
if (pc[2] > frame.callee.data.function->def->literalsLen)
gst_error(vm, GST_NO_UPVALUE);
temp = frame.callee.data.function->def->literals[pc[2]];
if (temp.type != GST_NIL)
gst_error(vm, "cannot create closure");
fn = gst_alloc(vm, sizeof(GstFunction));
@ -488,10 +468,16 @@ int gst_start(Gst *vm, GstValue func) {
/* Call the function */
if (temp.type == GST_CFUNCTION) {
/* Save current state to vm thread */
int status;
*((GstStackFrame *)(stack - GST_FRAME_SIZE)) = frame;
*vm->thread = thread;
v2 = temp.data.cfunction(vm);
goto ret;
vm->ret.type = GST_NIL;
status = temp.data.cfunction(vm);
v1 = vm->ret;
if (status == GST_RETURN_OK)
goto ret;
else
goto vm_error;
} else {
pc = temp.data.function->def->byteCode;
}
@ -502,29 +488,38 @@ int gst_start(Gst *vm, GstValue func) {
v2.type = GST_NIL;
goto ret;
case GST_OP_GET:
temp = gst_get(vm, stack[pc[2]], stack[pc[3]]);
stack[pc[1]] = temp;
pc += 4;
break;
case GST_OP_GET: /* Associative get */
{
const char *err;
err = gst_get(stack[pc[2]], stack[pc[3]], stack + pc[1]);
if (err != NULL)
gst_error(vm, err);
pc += 4;
break;
}
case GST_OP_SET:
gst_set(vm, stack[pc[1]], stack[pc[2]], stack[pc[3]]);
pc += 4;
break;
case GST_OP_SET: /* Associative set */
{
const char *err;
err = gst_set(vm, stack[pc[1]], stack[pc[2]], stack[pc[3]]);
if (err != NULL)
gst_error(vm, err);
pc += 4;
break;
}
case GST_OP_ERR:
vm->error = stack[pc[1]];
longjmp(vm->jump, 2);
case GST_OP_ERR: /* Throw error */
vm->ret = stack[pc[1]];
goto vm_error;
break;
case GST_OP_TRY:
case GST_OP_TRY: /* Begin try block */
frame.errorSlot = pc[1];
frame.errorJump = pc + *(uint32_t *)(pc + 2);
pc += 4;
break;
case GST_OP_UTY:
case GST_OP_UTY: /* End try block */
frame.errorJump = NULL;
pc++;
break;
@ -560,6 +555,36 @@ int gst_start(Gst *vm, GstValue func) {
*vm->thread = thread;
gst_maybe_collect(vm);
}
/* Handle errors from c functions and vm opcodes */
vm_error:
while (!frame.errorJump) {
/* Check for closure */
if (frame.env) {
frame.env->thread = NULL;
frame.env->stackOffset = frame.size;
frame.env->values = gst_alloc(vm, sizeof(GstValue) * frame.size);
gst_memcpy(frame.env->values,
thread.data + thread.count,
frame.size * sizeof(GstValue));
}
stack -= frame.prevSize + GST_FRAME_SIZE;
if (stack <= thread.data) {
thread.count = 0;
break;
}
frame = *((GstStackFrame *)(stack - GST_FRAME_SIZE));
}
/* If we completely unwind the stack we just return */
if (thread.count < GST_FRAME_SIZE)
return GST_RETURN_ERROR;
/* Jump to the error location */
pc = frame.errorJump;
/* Set error */
stack[frame.errorSlot] = vm->ret;
/* Resume vm */
goto mainloop;
}
/* Get an argument from the stack */
@ -567,7 +592,11 @@ GstValue gst_arg(Gst *vm, uint16_t index) {
GstValue *stack = vm->thread->data + vm->thread->count;
GstStackFrame *frame = (GstStackFrame *)(stack - GST_FRAME_SIZE);
uint16_t frameSize = frame->size;
gst_assert(vm, frameSize > index, "cannot get arg out of stack bounds");
if (frameSize <= index) {
GstValue ret;
ret.type = GST_NIL;
return ret;
}
return stack[index];
}
@ -576,7 +605,7 @@ void gst_set_arg(Gst* vm, uint16_t index, GstValue x) {
GstValue *stack = vm->thread->data + vm->thread->count;
GstStackFrame *frame = (GstStackFrame *)(stack - GST_FRAME_SIZE);
uint16_t frameSize = frame->size;
gst_assert(vm, frameSize > index, "cannot set arg out of stack bounds");
if (frameSize <= index) return;
stack[index] = x;
}
@ -590,7 +619,6 @@ uint16_t gst_count_args(Gst *vm) {
/* Initialize the VM */
void gst_init(Gst *vm) {
vm->ret.type = GST_NIL;
vm->error.type = GST_NIL;
vm->crash = NULL;
/* Garbage collection */
vm->blocks = NULL;

17
vm.h
View File

@ -4,23 +4,6 @@
#include "datatypes.h"
#include "value.h"
/* Exit from the VM normally */
#define gst_exit(vm, r) ((vm)->ret = (r), longjmp((vm)->jump, 1))
/* Bail from the VM with an error string. */
#define gst_error(vm, e) ((vm)->error = gst_load_cstring((vm), (e)), longjmp((vm)->jump, 2))
/* Crash. Not catchable, unlike error. */
#define gst_crash(vm, e) ((vm)->crash = (e), longjmp((vm)->jump, 3))
/* Error if the condition is false */
#define gst_assert(vm, cond, e) do \
{ if (!(cond)) { gst_error((vm), (e)); } } while (0)
/* Type assertion */
#define gst_assert_type(vm, f, t) \
gst_assert((vm), (f).type == (t), "Expected a different type.")
/* Initialize the VM */
void gst_init(Gst * vm);