#include "gravity_hash.h"
#include "gravity_array.h"
#include "gravity_debug.h"
#include "gravity_macros.h"
#include "gravity_vm.h"
#include "gravity_core.h"
#include "gravity_opcodes.h"
#include "gravity_memory.h"
#include "gravity_vmmacros.h"
#include "gravity_json.h"
static void gravity_gc_cleanup (gravity_vm *vm);
static void gravity_gc_transfer (gravity_vm *vm, gravity_object_t *obj);
static bool vm_set_superclass (gravity_vm *vm, gravity_object_t *obj);
static void gravity_gc_transform (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t *value, void *data);
static uint32_t cache_refcount = 0;
static gravity_value_t cache[GRAVITY_VTABLE_SIZE];
struct gravity_vm {
gravity_hash_t *context;
gravity_delegate_t *delegate;
gravity_fiber_t *fiber;
void *data;
uint32_t pc;
double time;
bool aborted;
uint32_t nanon;
char temp[64];
vm_transfer_cb transfer;
vm_cleanup_cb cleanup;
vm_filter_cb filter;
bool gcenabled;
gravity_int_t memallocated;
gravity_object_t *gchead;
gravity_int_t gcminthreshold;
gravity_int_t gcthreshold;
gravity_float_t gcratio;
gravity_int_t gccount;
gravity_object_r graylist;
gravity_object_r gcsave;
#if GRAVITY_VM_STATS
uint32_t nfrealloc;
uint32_t nsrealloc;
uint32_t nstat[GRAVITY_LATEST_OPCODE];
double tstat[GRAVITY_LATEST_OPCODE];
nanotime_t t;
#endif
};
static void report_runtime_error (gravity_vm *vm, error_type_t error_type, const char *format, ...) {
char buffer[1024];
va_list arg;
if (vm->aborted) return;
vm->aborted = true;
if (format) {
va_start (arg, format);
vsnprintf(buffer, sizeof(buffer), format, arg);
va_end (arg);
}
gravity_error_callback errorf = NULL;
if (vm->delegate) errorf = ((gravity_delegate_t *)vm->delegate)->error_callback;
if (errorf) {
void *data = ((gravity_delegate_t *)vm->delegate)->xdata;
errorf(error_type, buffer, ERROR_DESC_NONE, data);
} else {
printf("%s\n", buffer);
fflush(stdout);
}
}
static void gravity_cache_setup (void) {
++cache_refcount;
mem_check(false);
cache[GRAVITY_NOTFOUND_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_INTERNAL_NOTFOUND_NAME);
cache[GRAVITY_ADD_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_ADD_NAME);
cache[GRAVITY_SUB_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_SUB_NAME);
cache[GRAVITY_DIV_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_DIV_NAME);
cache[GRAVITY_MUL_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_MUL_NAME);
cache[GRAVITY_REM_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_REM_NAME);
cache[GRAVITY_AND_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_AND_NAME);
cache[GRAVITY_OR_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_OR_NAME);
cache[GRAVITY_CMP_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_CMP_NAME);
cache[GRAVITY_EQQ_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_EQQ_NAME);
cache[GRAVITY_ISA_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_ISA_NAME);
cache[GRAVITY_MATCH_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_MATCH_NAME);
cache[GRAVITY_NEG_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_NEG_NAME);
cache[GRAVITY_NOT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_NOT_NAME);
cache[GRAVITY_LSHIFT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_LSHIFT_NAME);
cache[GRAVITY_RSHIFT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_RSHIFT_NAME);
cache[GRAVITY_BAND_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_BAND_NAME);
cache[GRAVITY_BOR_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_BOR_NAME);
cache[GRAVITY_BXOR_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_BXOR_NAME);
cache[GRAVITY_BNOT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_BNOT_NAME);
cache[GRAVITY_LOAD_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_INTERNAL_LOAD_NAME);
cache[GRAVITY_LOADS_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_INTERNAL_LOADS_NAME);
cache[GRAVITY_LOADAT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_INTERNAL_LOADAT_NAME);
cache[GRAVITY_STORE_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_INTERNAL_STORE_NAME);
cache[GRAVITY_STOREAT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_INTERNAL_STOREAT_NAME);
cache[GRAVITY_INT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_CLASS_INT_NAME);
cache[GRAVITY_FLOAT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_CLASS_FLOAT_NAME);
cache[GRAVITY_BOOL_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_CLASS_BOOL_NAME);
cache[GRAVITY_STRING_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_CLASS_STRING_NAME);
cache[GRAVITY_EXEC_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_INTERNAL_EXEC_NAME);
mem_check(true);
}
static void gravity_cache_free (void) {
--cache_refcount;
if (cache_refcount > 0) return;
mem_check(false);
for (uint32_t index = 0; index <GRAVITY_VTABLE_SIZE; ++index) {
gravity_value_free(NULL, cache[index]);
}
mem_check(true);
}
gravity_value_t gravity_vm_keyindex (gravity_vm *vm, uint32_t index) {
#pragma unused (vm)
return cache[index];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
static void gravity_stack_dump (gravity_fiber_t *fiber) {
uint32_t index = 0;
for (gravity_value_t *stack = fiber->stack; stack < fiber->stacktop; ++stack) {
printf("[%05d]\t", index++);
if (!stack->isa) {printf("\n"); continue;}
gravity_value_dump(*stack, NULL, 0);
}
if (index) printf("\n\n");
}
#pragma clang diagnostic pop
static inline gravity_callframe_t *gravity_new_callframe (gravity_vm *vm, gravity_fiber_t *fiber) {
#pragma unused(vm)
if (fiber->framesalloc - fiber->nframes < 1) {
uint32_t new_size = fiber->framesalloc * 2;
fiber->frames = (gravity_callframe_t *) mem_realloc(fiber->frames, sizeof(gravity_callframe_t) * new_size);
fiber->framesalloc = new_size;
STAT_FRAMES_REALLOCATED(vm);
}
++fiber->nframes;
return &fiber->frames[fiber->nframes - 1];
}
static inline void gravity_check_stack (gravity_vm *vm, gravity_fiber_t *fiber, uint32_t rneeds, gravity_value_t **stackstart) {
#pragma unused(vm)
fiber->stacktop += rneeds;
uint32_t stack_size = (uint32_t)(fiber->stacktop - fiber->stack);
uint32_t stack_needed = MAXNUM(stack_size + rneeds, DEFAULT_MINSTACK_SIZE);
if (fiber->stackalloc >= stack_needed) return;
uint32_t new_size = power_of2_ceil(fiber->stackalloc + stack_needed);
gravity_value_t *old_stack = fiber->stack;
fiber->stack = (gravity_value_t *) mem_realloc(fiber->stack, sizeof(gravity_value_t) * new_size);
fiber->stackalloc = new_size;
STAT_STACK_REALLOCATED(vm);
if (fiber->stack == old_stack) return;
ptrdiff_t offset = (ptrdiff_t)(fiber->stack - old_stack);
for (uint32_t i=0; i < fiber->nframes; ++i) {
fiber->frames[i].stackstart += offset;
}
gravity_upvalue_t* upvalue = fiber->upvalues;
while (upvalue) {
upvalue->value += offset;
upvalue = upvalue->next;
}
fiber->stacktop += offset;
*stackstart += offset;
}
static gravity_upvalue_t *gravity_capture_upvalue (gravity_vm *vm, gravity_fiber_t *fiber, gravity_value_t *value) {
if (!fiber->upvalues) {
fiber->upvalues = gravity_upvalue_new(vm, value);
return fiber->upvalues;
}
gravity_upvalue_t *prevupvalue = NULL;
gravity_upvalue_t *upvalue = fiber->upvalues;
while (upvalue && upvalue->value > value) {
prevupvalue = upvalue;
upvalue = upvalue->next;
}
if (upvalue != NULL && upvalue->value == value) return upvalue;
gravity_upvalue_t *newvalue = gravity_upvalue_new(vm, value);
if (prevupvalue == NULL) fiber->upvalues = newvalue;
else prevupvalue->next = newvalue;
newvalue->next = upvalue;
return newvalue;
}
static void gravity_close_upvalues (gravity_fiber_t *fiber, gravity_value_t *level) {
while (fiber->upvalues != NULL && fiber->upvalues->value >= level) {
gravity_upvalue_t *upvalue = fiber->upvalues;
upvalue->closed = *upvalue->value;
upvalue->value = &upvalue->closed;
fiber->upvalues = upvalue->next;
}
}
static void gravity_vm_loadclass (gravity_vm *vm, gravity_class_t *c) {
gravity_hash_transform(c->htable, gravity_gc_transform, (void *)vm);
gravity_class_t *meta = gravity_class_get_meta(c);
gravity_hash_transform(meta->htable, gravity_gc_transform, (void *)vm);
}
static bool gravity_vm_exec (gravity_vm *vm) {
DECLARE_DISPATCH_TABLE;
gravity_fiber_t *fiber = vm->fiber;
gravity_delegate_t *delegate = vm->delegate;
gravity_callframe_t *frame;
gravity_function_t *func;
gravity_value_t *stackstart;
register uint32_t *ip;
register uint32_t inst;
register opcode_t op;
LOAD_FRAME();
DEBUG_CALL("Executing", func);
if ((ip == NULL) || (!func->bytecode) || (func->ninsts == 0)) return true;
DEBUG_STACK();
while (1) {
INTERPRET_LOOP {
CASE_CODE(NOP): {
DEBUG_VM("NOP");
DISPATCH();
}
CASE_CODE(MOVE): {
OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t r2);
DEBUG_VM("MOVE %d %d", r1, r2);
SETVALUE(r1, STACK_GET(r2));
DISPATCH();
}
CASE_CODE(LOAD):
CASE_CODE(LOADS):
CASE_CODE(LOADAT):{
OPCODE_GET_TWO8bit_ONE10bit(inst, const uint32_t r1, const uint32_t r2, const uint32_t r3);
DEBUG_VM("%s %d %d %d", (op == LOAD) ? "LOAD" : ((op == LOADAT) ? "LOADAT" : "LOADS"), r1, r2, r3);
DEFINE_STACK_VARIABLE(v2,r2);
DEFINE_INDEX_VARIABLE(v3,r3);
PREPARE_FUNC_CALL2(closure, v2, v3, (op == LOAD) ? GRAVITY_LOAD_INDEX : ((op == LOADAT) ? GRAVITY_LOADAT_INDEX : GRAVITY_LOADS_INDEX), rwin);
STORE_FRAME();
execute_load_function:
switch(closure->f->tag) {
case EXEC_TYPE_NATIVE: {
PUSH_FRAME(closure, &stackstart[rwin], r1, 2);
} break;
case EXEC_TYPE_INTERNAL: {
SETVALUE(r1, VALUE_FROM_NULL);
if (!closure->f->internal(vm, &stackstart[rwin], 2, r1)) {
if (VALUE_ISA_CLOSURE(STACK_GET(r1))) {
closure = VALUE_AS_CLOSURE(STACK_GET(r1));
SETVALUE(r1, VALUE_FROM_NULL);
goto execute_load_function;
}
fiber = vm->fiber;
if (fiber == NULL) return true;
if (fiber->error) RUNTIME_FIBER_ERROR(fiber->error);
}
} break;
case EXEC_TYPE_BRIDGED: {
ASSERT(delegate->bridge_getvalue, "bridge_getvalue delegate callback is mandatory");
if (!delegate->bridge_getvalue(vm, closure->f->xdata, v2, VALUE_AS_CSTRING(v3), r1)) {
if (fiber->error) RUNTIME_FIBER_ERROR(fiber->error);
}
} break;
case EXEC_TYPE_SPECIAL: {
if (!closure->f->special[EXEC_TYPE_SPECIAL_GETTER]) RUNTIME_ERROR("Missing special getter function for property %s", VALUE_AS_CSTRING(v3));
closure = closure->f->special[EXEC_TYPE_SPECIAL_GETTER];
goto execute_load_function;
} break;
}
LOAD_FRAME();
SYNC_STACKTOP(closure, _rneed);
DISPATCH();
}
CASE_CODE(LOADI): {
OPCODE_GET_ONE8bit_SIGN_ONE17bit(inst, const uint32_t r1, const int32_t value);
DEBUG_VM("LOADI %d %d", r1, value);
SETVALUE_INT(r1, value);
DISPATCH();
}
CASE_CODE(LOADK): {
OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t index);
DEBUG_VM("LOADK %d %d", r1, index);
if (index < CPOOL_INDEX_MAX) {
gravity_value_t v = gravity_function_cpool_get(func, index);
SETVALUE(r1, v);
DISPATCH();
}
switch (index) {
case CPOOL_VALUE_SUPER: {
gravity_class_t *super = gravity_value_getsuper(STACK_GET(0));
SETVALUE(r1, (super) ? VALUE_FROM_OBJECT(super) : VALUE_FROM_NULL);
} break;
case CPOOL_VALUE_ARGUMENTS: SETVALUE(r1, VALUE_FROM_OBJECT(frame->args)); break;
case CPOOL_VALUE_NULL: SETVALUE(r1, VALUE_FROM_NULL); break;
case CPOOL_VALUE_UNDEFINED: SETVALUE(r1, VALUE_FROM_UNDEFINED); break;
case CPOOL_VALUE_TRUE: SETVALUE(r1, VALUE_FROM_TRUE); break;
case CPOOL_VALUE_FALSE: SETVALUE(r1, VALUE_FROM_FALSE); break;
case CPOOL_VALUE_FUNC: SETVALUE(r1, VALUE_FROM_OBJECT(frame->closure)); break;
default: RUNTIME_ERROR("Unknown LOADK index"); break;
}
DISPATCH();
}
CASE_CODE(LOADG): {
OPCODE_GET_ONE8bit_ONE18bit(inst, uint32_t r1, int32_t index);
DEBUG_VM("LOADG %d %d", r1, index);
gravity_value_t key = gravity_function_cpool_get(func, index);
gravity_value_t *v = gravity_hash_lookup(vm->context, key);
if (!v) RUNTIME_ERROR("Unable to find object %s", VALUE_AS_CSTRING(key));
SETVALUE(r1, *v);
DISPATCH();
}
CASE_CODE(LOADU): {
OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t r2);
DEBUG_VM("LOADU %d %d", r1, r2);
gravity_upvalue_t *upvalue = frame->closure->upvalue[r2];
SETVALUE(r1, *upvalue->value);
DISPATCH();
}
CASE_CODE(STORE):
CASE_CODE(STOREAT):{
OPCODE_GET_TWO8bit_ONE10bit(inst, const uint32_t r1, const uint32_t r2, const uint32_t r3);
DEBUG_VM("%s %d %d %d", (op == STORE) ? "STORE" : "STOREAT", r1, r2,r3);
DEFINE_STACK_VARIABLE(v1,r1);
DEFINE_STACK_VARIABLE(v2,r2);
DEFINE_INDEX_VARIABLE(v3,r3);
PREPARE_FUNC_CALL3(closure, v2, v3, v1, (op == STORE) ? GRAVITY_STORE_INDEX : GRAVITY_STOREAT_INDEX, rwin);
STORE_FRAME();
execute_store_function:
switch(closure->f->tag) {
case EXEC_TYPE_NATIVE: {
SETVALUE(rwin+1, v1);
PUSH_FRAME(closure, &stackstart[rwin], r1, 2);
} break;
case EXEC_TYPE_INTERNAL: {
SETVALUE(r1, VALUE_FROM_NULL);
if (!closure->f->internal(vm, &stackstart[rwin], 2, r1)) {
if (VALUE_ISA_CLOSURE(STACK_GET(r1))) {
closure = VALUE_AS_CLOSURE(STACK_GET(r1));
SETVALUE(r1, VALUE_FROM_NULL);
goto execute_store_function;
}
fiber = vm->fiber;
if (fiber == NULL) return true;
if (fiber->error) RUNTIME_FIBER_ERROR(fiber->error);
}
} break;
case EXEC_TYPE_BRIDGED: {
ASSERT(delegate->bridge_setvalue, "bridge_setvalue delegate callback is mandatory");
if (!delegate->bridge_setvalue(vm, closure->f->xdata, v2, VALUE_AS_CSTRING(v3), v1)) {
if (fiber->error) RUNTIME_FIBER_ERROR(fiber->error);
}
} break;
case EXEC_TYPE_SPECIAL: {
if (!closure->f->special[EXEC_TYPE_SPECIAL_SETTER]) RUNTIME_ERROR("Missing special setter function for property %s", VALUE_AS_CSTRING(v3));
closure = closure->f->special[EXEC_TYPE_SPECIAL_SETTER];
goto execute_store_function;
} break;
}
LOAD_FRAME();
SYNC_STACKTOP(closure, _rneed);
DISPATCH();
}
CASE_CODE(STOREG): {
OPCODE_GET_ONE8bit_ONE18bit(inst, uint32_t r1, int32_t index);
DEBUG_VM("STOREG %d %d", r1, index);
gravity_value_t key = gravity_function_cpool_get(func, index);
gravity_value_t v = STACK_GET(r1);
gravity_hash_insert(vm->context, key, v);
DISPATCH();
}
CASE_CODE(STOREU): {
OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t r2);
DEBUG_VM("STOREU %d %d", r1, r2);
gravity_upvalue_t *upvalue = frame->closure->upvalue[r2];
*upvalue->value = STACK_GET(r1);
DISPATCH();
}
CASE_CODE(EQQ):
CASE_CODE(NEQQ): {
DECODE_BINARY_OPERATION(r1,r2,r3);
DEFINE_STACK_VARIABLE(v2,r2);
DEFINE_STACK_VARIABLE(v3,r3);
PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_EQQ_INDEX, rwin);
CALL_FUNC(EQQ, closure, r1, 2, rwin);
register gravity_int_t result = STACK_GET(r1).n;
SETVALUE_BOOL(r1, (op == EQQ) ? result : !result);
DISPATCH();
}
CASE_CODE(ISA):
CASE_CODE(MATCH): {
DECODE_BINARY_OPERATION(r1, r2, r3);
DEFINE_STACK_VARIABLE(v2,r2);
DEFINE_STACK_VARIABLE(v3,r3);
PREPARE_FUNC_CALL2(closure, v2, v3, (op == ISA) ? GRAVITY_ISA_INDEX : GRAVITY_MATCH_INDEX, rwin);
CALL_FUNC(ISA, closure, r1, 2, rwin);
DISPATCH();
}
CASE_CODE(LT):
CASE_CODE(GT):
CASE_CODE(EQ):
CASE_CODE(LEQ):
CASE_CODE(GEQ):
CASE_CODE(NEQ): {
DECODE_BINARY_OPERATION(r1,r2,r3);
DEFINE_STACK_VARIABLE(v2,r2);
DEFINE_STACK_VARIABLE(v3,r3);
if ((VALUE_ISA_BOOL(v2) && (VALUE_ISA_BOOL(v3))) || (VALUE_ISA_UNDEFINED(v2) || (VALUE_ISA_UNDEFINED(v3)))) {
register gravity_int_t eq_result = (v2.isa == v3.isa) && (v2.n == v3.n);
SETVALUE(r1, VALUE_FROM_BOOL((op == EQ) ? eq_result : !eq_result));
DISPATCH();
} else if (VALUE_ISA_INT(v2) && VALUE_ISA_INT(v3)) {
if (v2.n == v3.n) SETVALUE(r1, VALUE_FROM_INT(0));
else SETVALUE(r1, VALUE_FROM_INT((v2.n > v3.n) ? 1 : -1));
} else {
PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_CMP_INDEX, rwin);
CALL_FUNC(CMP, closure, r1, 2, rwin);
}
register gravity_int_t result = STACK_GET(r1).n;
switch(op) {
case LT: SETVALUE_BOOL(r1, result < 0); break;
case GT: SETVALUE_BOOL(r1, result > 0); break;
case EQ: SETVALUE_BOOL(r1, result == 0); break;
case LEQ: SETVALUE_BOOL(r1, result <= 0); break;
case GEQ: SETVALUE_BOOL(r1, result >= 0); break;
case NEQ: SETVALUE_BOOL(r1, result != 0); break;
default: assert(0);
}
uint32_t inext = *ip++;
if ((STACK_GET(r1).n == 0) && (OPCODE_GET_OPCODE(inext) == JUMPF)) {
OPCODE_GET_LAST18bit(inext, int32_t value);
DEBUG_VM("JUMPF %d %d", (int)result, value);
ip = COMPUTE_JUMP(value);
DISPATCH();
}
--ip;
DISPATCH();
}
CASE_CODE(LSHIFT): {
DECODE_BINARY_OPERATION(r1, r2, r3);
CHECK_FAST_BINARY_BIT(r1, r2, r3, v2, v3, <<);
PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_LSHIFT_INDEX, rwin);
CALL_FUNC(LSHIFT, closure, r1, 2, rwin);
DISPATCH();
}
CASE_CODE(RSHIFT): {
DECODE_BINARY_OPERATION(r1, r2, r3);
CHECK_FAST_BINARY_BIT(r1, r2, r3, v2, v3, >>);
PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_RSHIFT_INDEX, rwin);
CALL_FUNC(RSHIFT, closure, r1, 2, rwin);
DISPATCH();
}
CASE_CODE(BAND): {
DECODE_BINARY_OPERATION(r1, r2, r3);
CHECK_FAST_BINARY_BIT(r1, r2, r3, v2, v3, &);
PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_BAND_INDEX, rwin);
CALL_FUNC(BAND, closure, r1, 2, rwin);
DISPATCH();
}
CASE_CODE(BOR): {
DECODE_BINARY_OPERATION(r1, r2, r3);
CHECK_FAST_BINARY_BIT(r1, r2, r3, v2, v3, |);
PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_BOR_INDEX, rwin);
CALL_FUNC(BOR, closure, r1, 2, rwin);
DISPATCH();
}
CASE_CODE(BXOR):{
DECODE_BINARY_OPERATION(r1, r2, r3);
CHECK_FAST_BINARY_BIT(r1, r2, r3, v2, v3, ^);
PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_BXOR_INDEX, rwin);
CALL_FUNC(BXOR, closure, r1, 2, rwin);
DISPATCH();
}
CASE_CODE(ADD): {
DECODE_BINARY_OPERATION(r1, r2, r3);
CHECK_FAST_BINARY_MATH(r1, r2, r3, v2, v3, +, NO_CHECK);
PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_ADD_INDEX, rwin);
CALL_FUNC(ADD, closure, r1, 2, rwin);
DISPATCH();
}
CASE_CODE(SUB): {
DECODE_BINARY_OPERATION(r1, r2, r3);
CHECK_FAST_BINARY_MATH(r1, r2, r3, v2, v3, -, NO_CHECK);
PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_SUB_INDEX, rwin);
CALL_FUNC(SUB, closure, r1, 2, rwin);
DISPATCH();
}
CASE_CODE(DIV): {
DECODE_BINARY_OPERATION(r1, r2, r3);
CHECK_FAST_BINARY_MATH(r1, r2, r3, v2, v3, /, CHECK_ZERO(v3));
PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_DIV_INDEX, rwin);
CALL_FUNC(DIV, closure, r1, 2, rwin);
DISPATCH();
}
CASE_CODE(MUL): {
DECODE_BINARY_OPERATION(r1, r2, r3);
CHECK_FAST_BINARY_MATH(r1, r2, r3, v2, v3, *, NO_CHECK);
PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_MUL_INDEX, rwin);
CALL_FUNC(MUL, closure, r1, 2, rwin);
DISPATCH();
}
CASE_CODE(REM): {
DECODE_BINARY_OPERATION(r1, r2, r3);
CHECK_FAST_BINARY_REM(r1, r2, r3, v2, v3);
PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_REM_INDEX, rwin);
CALL_FUNC(REM, closure, r1, 2, rwin);
DISPATCH();
}
CASE_CODE(AND): {
DECODE_BINARY_OPERATION(r1, r2, r3);
CHECK_FAST_BINARY_BOOL(r1, r2, r3, v2, v3, &&);
PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_AND_INDEX, rwin);
CALL_FUNC(AND, closure, r1, 2, rwin);
DISPATCH();
}
CASE_CODE(OR): {
DECODE_BINARY_OPERATION(r1, r2, r3);
CHECK_FAST_BINARY_BOOL(r1, r2, r3, v2, v3, ||);
PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_OR_INDEX, rwin);
CALL_FUNC(OR, closure, r1, 2, rwin);
DISPATCH();
}
CASE_CODE(NEG): {
DECODE_BINARY_OPERATION(r1, r2, r3);
#pragma unused(r3)
CHECK_FAST_UNARY_MATH(r1, r2, v2, -);
PREPARE_FUNC_CALL1(closure, v2, GRAVITY_NEG_INDEX, rwin);
CALL_FUNC(NEG, closure, r1, 1, rwin);
DISPATCH();
}
CASE_CODE(NOT): {
DECODE_BINARY_OPERATION(r1, r2, r3);
#pragma unused(r3)
CHECK_FAST_UNARY_BOOL(r1, r2, v2, !);
PREPARE_FUNC_CALL1(closure, v2, GRAVITY_NOT_INDEX, rwin);
CALL_FUNC(NOT, closure, r1, 1, rwin);
DISPATCH();
}
CASE_CODE(BNOT): {
DECODE_BINARY_OPERATION(r1, r2, r3);
#pragma unused(r3)
DEFINE_STACK_VARIABLE(v2,r2);
if (VALUE_ISA_INT(v2)) {SETVALUE(r1, VALUE_FROM_INT(~v2.n)); DISPATCH();}
PREPARE_FUNC_CALL1(closure, v2, GRAVITY_BNOT_INDEX, rwin);
CALL_FUNC(BNOT, closure, r1, 1, rwin);
DISPATCH();
}
CASE_CODE(JUMPF): {
OPCODE_GET_ONE8bit_FLAG_ONE17bit(inst, const uint32_t r1, const uint32_t flag, const int32_t value);
DEBUG_VM("JUMPF %d %d (flag %d)", r1, value, flag);
if (flag) {
if ((VALUE_ISA_BOOL(STACK_GET(r1))) && (GETVALUE_INT(STACK_GET(r1)) == 0)) ip = COMPUTE_JUMP(value);
DISPATCH();
}
DEFINE_STACK_VARIABLE(v1, r1);
if (VALUE_ISA_NULL(v1) || (VALUE_ISA_UNDEFINED(v1))) {ip = COMPUTE_JUMP(value);}
else if (VALUE_ISA_BOOL(v1) || (VALUE_ISA_INT(v1))) {if (GETVALUE_INT(v1) == 0) ip = COMPUTE_JUMP(value);}
else if (VALUE_ISA_FLOAT(v1)) {if (GETVALUE_FLOAT(v1) == 0.0) ip = COMPUTE_JUMP(value);}
else if (VALUE_ISA_STRING(v1)) {if (VALUE_AS_STRING(v1)->len == 0) ip = COMPUTE_JUMP(value);}
else {
gravity_closure_t *closure = (gravity_closure_t *)gravity_class_lookup_closure(gravity_value_getclass(v1), cache[GRAVITY_BOOL_INDEX]);
if (closure) {
uint32_t rwin = FN_COUNTREG(func, frame->nargs);
uint32_t _rneed = FN_COUNTREG(closure->f, 1);
gravity_check_stack(vm, fiber, _rneed, &stackstart);
SETVALUE(rwin, v1);
CALL_FUNC(JUMPF, closure, rwin, 2, rwin);
gravity_int_t result = STACK_GET(rwin).n;
if (!result) ip = COMPUTE_JUMP(value);
}
}
DISPATCH();
}
CASE_CODE(JUMP): {
OPCODE_GET_ONE26bit(inst, const uint32_t value);
DEBUG_VM("JUMP %d", value);
ip = COMPUTE_JUMP(value);
DISPATCH();
}
CASE_CODE(CALL): {
OPCODE_GET_THREE8bit(inst, const uint32_t r1, const uint32_t r2, register uint32_t r3);
DEBUG_VM("CALL %d %d %d", r1, r2, r3);
DEBUG_STACK();
const uint32_t rwin = r2 + 1;
gravity_value_t v = STACK_GET(r2);
gravity_closure_t *closure = NULL;
if (VALUE_ISA_CLOSURE(v)) {
closure = VALUE_AS_CLOSURE(v);
} else {
closure = (gravity_closure_t *)gravity_class_lookup_closure(gravity_value_getclass(v), cache[GRAVITY_EXEC_INDEX]);
}
if (!closure) RUNTIME_ERROR("Unable to call object (in function %s)", func->identifier);
uint32_t _rneed = FN_COUNTREG(closure->f, r3);
gravity_check_stack(vm, fiber, _rneed, &stackstart);
while (r3 < closure->f->nparams) {
SETVALUE(rwin+r3, VALUE_FROM_UNDEFINED);
++r3;
}
DEBUG_STACK();
STORE_FRAME();
execute_call_function:
switch(closure->f->tag) {
case EXEC_TYPE_NATIVE: {
PUSH_FRAME(closure, &stackstart[rwin], r1, r3);
} break;
case EXEC_TYPE_INTERNAL: {
SETVALUE(r1, VALUE_FROM_NULL);
if (!closure->f->internal(vm, &stackstart[rwin], r3, r1)) {
if (VALUE_ISA_CLOSURE(STACK_GET(r1))) {
closure = VALUE_AS_CLOSURE(STACK_GET(r1));
SETVALUE(r1, VALUE_FROM_NULL);
goto execute_call_function;
}
fiber = vm->fiber;
if (fiber == NULL) return true;
if (fiber->error) RUNTIME_FIBER_ERROR(fiber->error);
}
} break;
case EXEC_TYPE_BRIDGED: {
bool result;
if (VALUE_ISA_CLASS(v)) {
ASSERT(delegate->bridge_initinstance, "bridge_initinstance delegate callback is mandatory");
gravity_instance_t *instance = (gravity_instance_t *)VALUE_AS_OBJECT(stackstart[rwin]);
result = delegate->bridge_initinstance(vm, closure->f->xdata, instance, &stackstart[rwin], r3);
SETVALUE(r1, VALUE_FROM_OBJECT(instance));
} else {
ASSERT(delegate->bridge_execute, "bridge_execute delegate callback is mandatory");
result = delegate->bridge_execute(vm, closure->f->xdata, &stackstart[rwin], r3, r1);
}
if (!result && fiber->error) RUNTIME_FIBER_ERROR(fiber->error);
} break;
case EXEC_TYPE_SPECIAL:
RUNTIME_ERROR("Unable to handle a special function in current context");
break;
}
LOAD_FRAME();
SYNC_STACKTOP(closure, _rneed);
DISPATCH();
}
CASE_CODE(RET0):
CASE_CODE(RET): {
gravity_value_t result;
if (op == RET0) {
DEBUG_VM("RET0");
result = VALUE_FROM_NULL;
} else {
OPCODE_GET_ONE8bit(inst, const uint32_t r1);
DEBUG_VM("RET %d", r1);
result = STACK_GET(r1);
}
ASSERT(fiber->nframes > 0, "Number of active frames cannot be 0.");
--fiber->nframes;
gravity_close_upvalues(fiber, stackstart);
if (frame->outloop) {
fiber->result = result;
return true;
}
uint32_t dest = fiber->frames[fiber->nframes].dest;
if (fiber->nframes == 0) {
if (fiber->caller == NULL) {fiber->result = result; return true;}
fiber = fiber->caller;
vm->fiber = fiber;
} else {
fiber->stacktop = frame->stackstart;
}
LOAD_FRAME();
DEBUG_CALL("Resuming", func);
SETVALUE(dest, result);
DISPATCH();
}
CASE_CODE(HALT): {
DEBUG_VM("HALT");
return true;
}
CASE_CODE(SWITCH): {
ASSERT(0, "To be implemented");
DISPATCH();
}
CASE_CODE(MAPNEW): {
OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t n);
DEBUG_VM("MAPNEW %d %d", r1, n);
gravity_map_t *map = gravity_map_new(vm, n);
SETVALUE(r1, VALUE_FROM_OBJECT(map));
DISPATCH();
}
CASE_CODE(LISTNEW): {
OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t n);
DEBUG_VM("LISTNEW %d %d", r1, n);
gravity_list_t *list = gravity_list_new(vm, n);
SETVALUE(r1, VALUE_FROM_OBJECT(list));
DISPATCH();
}
CASE_CODE(RANGENEW): {
OPCODE_GET_THREE8bit_ONE2bit(inst, const uint32_t r1, const uint32_t r2, const uint32_t r3, const bool flag);
DEBUG_VM("RANGENEW %d %d %d (flag %d)", r1, r2, r3, flag);
if ((!VALUE_ISA_INT(STACK_GET(r2))) || (!VALUE_ISA_INT(STACK_GET(r3))))
RUNTIME_ERROR("Unable to build Range from a non Int value");
gravity_range_t *range = gravity_range_new(vm, VALUE_AS_INT(STACK_GET(r2)), VALUE_AS_INT(STACK_GET(r3)), !flag);
SETVALUE(r1, VALUE_FROM_OBJECT(range));
DISPATCH();
}
CASE_CODE(SETLIST): {
OPCODE_GET_TWO8bit_ONE10bit(inst, uint32_t r1, uint32_t r2, const uint32_t r3);
DEBUG_VM("SETLIST %d %d", r1, r2);
gravity_value_t v1 = STACK_GET(r1);
bool v1_is_map = VALUE_ISA_MAP(v1);
if (r2 == 0) {
gravity_value_t v2 = gravity_function_cpool_get(func, r3);
if (v1_is_map) {
gravity_map_t *map = VALUE_AS_MAP(v1);
gravity_map_append_map(vm, map, VALUE_AS_MAP(v2));
} else {
gravity_list_t *list = VALUE_AS_LIST(v1);
gravity_list_append_list(vm, list, VALUE_AS_LIST(v2));
}
DISPATCH();
}
if (v1_is_map) {
gravity_map_t *map = VALUE_AS_MAP(v1);
while (r2) {
gravity_value_t key = STACK_GET(++r1);
gravity_value_t value = STACK_GET(++r1);
gravity_hash_insert(map->hash, key, value);
--r2;
}
} else {
gravity_list_t *list = VALUE_AS_LIST(v1);
while (r2) {
marray_push(gravity_value_t, list->array, STACK_GET(++r1));
--r2;
}
}
DISPATCH();
}
CASE_CODE(CLOSURE): {
OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t index);
DEBUG_VM("CLOSURE %d %d", r1, index);
gravity_value_t v = gravity_function_cpool_get(func, index);
if (!VALUE_ISA_FUNCTION(v)) RUNTIME_ERROR("Unable to create a closure from a non function object.");
gravity_function_t *f = VALUE_AS_FUNCTION(v);
gravity_closure_t *closure = gravity_closure_new(vm, f);
for (uint16_t i=0; i<f->nupvalues; ++i) {
inst = *ip++;
op = (opcode_t)OPCODE_GET_OPCODE(inst);
OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t p1, const uint32_t p2);
ASSERT(op == MOVE, "Wrong OPCODE in CLOSURE statement");
closure->upvalue[i] = (p2) ? gravity_capture_upvalue (vm, fiber, &stackstart[p1]) : frame->closure->upvalue[p1];
}
SETVALUE(r1, VALUE_FROM_OBJECT(closure));
DISPATCH();
}
CASE_CODE(CLOSE): {
OPCODE_GET_ONE8bit(inst, const uint32_t r1);
DEBUG_VM("CLOSE %d", r1);
gravity_close_upvalues(fiber, &stackstart[r1]);
DISPATCH();
}
CASE_CODE(RESERVED1):
CASE_CODE(RESERVED2):
CASE_CODE(RESERVED3):
CASE_CODE(RESERVED4):
CASE_CODE(RESERVED5):
CASE_CODE(RESERVED6):{
RUNTIME_ERROR("Opcode not implemented in this VM version.");
DISPATCH();
}
}
INC_PC;
};
return true;
}
gravity_vm *gravity_vm_new (gravity_delegate_t *delegate) {
gravity_vm *vm = mem_alloc(sizeof(gravity_vm));
if (!vm) return NULL;
vm->transfer = gravity_gc_transfer;
vm->cleanup = gravity_gc_cleanup;
vm->pc = 0;
vm->delegate = delegate;
vm->context = gravity_hash_create(DEFAULT_CONTEXT_SIZE, gravity_value_hash, gravity_value_equals, NULL, NULL);
vm->gcenabled = true;
vm->gcminthreshold = DEFAULT_CG_MINTHRESHOLD;
vm->gcthreshold = DEFAULT_CG_THRESHOLD;
vm->gcratio = DEFAULT_CG_RATIO;
vm->memallocated = 0;
marray_init(vm->graylist);
marray_init(vm->gcsave);
gravity_core_register(vm);
gravity_cache_setup();
RESET_STATS(vm);
return vm;
}
gravity_vm *gravity_vm_newmini (void) {
gravity_vm *vm = mem_alloc(sizeof(gravity_vm));
return vm;
}
void gravity_vm_free (gravity_vm *vm) {
if (!vm) return;
if (vm->context) gravity_cache_free();
gravity_vm_cleanup(vm);
if (vm->context) gravity_hash_free(vm->context);
marray_destroy(vm->gcsave);
marray_destroy(vm->graylist);
mem_free(vm);
}
inline gravity_value_t gravity_vm_lookup (gravity_vm *vm, gravity_value_t key) {
gravity_value_t *value = gravity_hash_lookup(vm->context, key);
return (value) ? *value : VALUE_NOT_VALID;
}
inline gravity_closure_t *gravity_vm_fastlookup (gravity_vm *vm, gravity_class_t *c, int index) {
#pragma unused(vm)
return (gravity_closure_t *)gravity_class_lookup_closure(c, cache[index]);
}
inline gravity_value_t gravity_vm_getvalue (gravity_vm *vm, const char *key, uint32_t keylen) {
STATICVALUE_FROM_STRING(k, key, keylen);
return gravity_vm_lookup(vm, k);
}
inline void gravity_vm_setvalue (gravity_vm *vm, const char *key, gravity_value_t value) {
gravity_hash_insert(vm->context, VALUE_FROM_CSTRING(vm, key), value);
}
double gravity_vm_time (gravity_vm *vm) {
return vm->time;
}
gravity_value_t gravity_vm_result (gravity_vm *vm) {
gravity_value_t result = vm->fiber->result;
vm->fiber->result = VALUE_FROM_NULL;
return result;
}
gravity_delegate_t *gravity_vm_delegate (gravity_vm *vm) {
return vm->delegate;
}
gravity_fiber_t *gravity_vm_fiber (gravity_vm* vm) {
return vm->fiber;
}
void gravity_vm_setfiber(gravity_vm* vm, gravity_fiber_t *fiber) {
vm->fiber = fiber;
}
void gravity_vm_seterror (gravity_vm* vm, const char *format, ...) {
gravity_fiber_t *fiber = vm->fiber;
if (fiber->error) mem_free(fiber->error);
size_t err_size = 2048;
fiber->error = mem_alloc(err_size);
va_list arg;
va_start (arg, format);
vsnprintf(fiber->error, err_size, (format) ? format : "%s", arg);
va_end (arg);
}
void gravity_vm_seterror_string (gravity_vm* vm, const char *s) {
gravity_fiber_t *fiber = vm->fiber;
if (fiber->error) mem_free(fiber->error);
fiber->error = (char *)string_dup(s);
}
#if GRAVITY_VM_STATS
static void gravity_vm_stats (gravity_vm *vm) {
printf("\n============================================\n");
printf("%12s %10s %20s\n", "OPCODE", "USAGE", "MICROBENCH (ms)");
printf("============================================\n");
double total = 0.0;
for (uint32_t i=0; i<GRAVITY_LATEST_OPCODE; ++i) {
if (vm->nstat[i]) {
total += vm->tstat[i];
}
}
for (uint32_t i=0; i<GRAVITY_LATEST_OPCODE; ++i) {
if (vm->nstat[i]) {
uint32_t n = vm->nstat[i];
double d = vm->tstat[i] / (double)n;
double p = (vm->tstat[i] * 100) / total;
printf("%12s %*d %*.4f (%.2f%%)\n", opcode_name((opcode_t)i), 10, n, 11, d, p);
}
}
printf("============================================\n");
printf("# Frames reallocs: %d (%d)\n", vm->nfrealloc, vm->fiber->framesalloc);
printf("# Stack reallocs: %d (%d)\n", vm->nsrealloc, vm->fiber->stackalloc);
printf("============================================\n");
}
#endif
bool gravity_vm_runclosure (gravity_vm *vm, gravity_closure_t *closure, gravity_value_t selfvalue, gravity_value_t params[], uint16_t nparams) {
if (vm->aborted) return false;
gravity_function_t *f = closure->f;
if ((f->tag == EXEC_TYPE_NATIVE) && ((!f->bytecode) || (f->ninsts == 0))) return true;
gravity_fiber_t *fiber = vm->fiber;
gravity_value_t *stackstart = NULL;
uint32_t rwin = 0;
DEBUG_STACK();
if (fiber->nframes) {
gravity_callframe_t *frame = &fiber->frames[fiber->nframes - 1];
stackstart = frame->stackstart;
uint32_t *ip = frame->ip;
rwin = FN_COUNTREG(frame->closure->f, frame->nargs);
gravity_check_stack(vm, vm->fiber, FN_COUNTREG(f,nparams+1), &stackstart);
SETVALUE(rwin, selfvalue);
for (uint16_t i=0; i<nparams; ++i) {
SETVALUE(rwin+i+1, params[i]);
}
STORE_FRAME();
PUSH_FRAME(closure, &stackstart[rwin], rwin, nparams);
SETFRAME_OUTLOOP(cframe);
} else {
gravity_fiber_reassign(vm->fiber, closure, nparams+1);
stackstart = vm->fiber->stack;
SETVALUE(rwin, selfvalue);
for (uint16_t i=0; i<nparams; ++i) {
SETVALUE(rwin+i+1, params[i]);
}
}
DEBUG_STACK();
bool result = false;
switch (f->tag) {
case EXEC_TYPE_NATIVE:
result = gravity_vm_exec(vm);
break;
case EXEC_TYPE_INTERNAL:
result = f->internal(vm, &stackstart[rwin], nparams, GRAVITY_FIBER_REGISTER);
break;
case EXEC_TYPE_BRIDGED:
if (vm && vm->delegate && vm->delegate->bridge_execute)
result = vm->delegate->bridge_execute(vm, f->xdata, &stackstart[rwin], nparams, GRAVITY_FIBER_REGISTER);
break;
case EXEC_TYPE_SPECIAL:
result = false;
break;
}
if (f->tag != EXEC_TYPE_NATIVE) --fiber->nframes;
DEBUG_STACK();
return result;
}
bool gravity_vm_run (gravity_vm *vm, gravity_closure_t *closure) {
vm->fiber = gravity_fiber_new(vm, closure, 0, 0);
gravity_vm_exec(vm);
gravity_value_t main = gravity_vm_getvalue(vm, MAIN_FUNCTION, (uint32_t)strlen(MAIN_FUNCTION));
if (!VALUE_ISA_CLOSURE(main)) {
report_runtime_error(vm, GRAVITY_ERROR_RUNTIME, "%s", "Unable to find main function.");
return false;
}
gravity_closure_t *main_closure = VALUE_AS_CLOSURE(main);
gravity_fiber_reassign(vm->fiber, main_closure, 0);
RESET_STATS(vm);
nanotime_t tstart = nanotime();
bool result = gravity_vm_exec(vm);
nanotime_t tend = nanotime();
vm->time = millitime(tstart, tend);
PRINT_STATS(vm);
return result;
}
void gravity_vm_setslot (gravity_vm *vm, gravity_value_t value, uint32_t index) {
if (index == GRAVITY_FIBER_REGISTER) {
vm->fiber->result = value;
return;
}
gravity_callframe_t *frame = &(vm->fiber->frames[vm->fiber->nframes-1]);
frame->stackstart[index] = value;
}
gravity_value_t gravity_vm_getslot (gravity_vm *vm, uint32_t index) {
gravity_callframe_t *frame = &(vm->fiber->frames[vm->fiber->nframes-1]);
return frame->stackstart[index];
}
void gravity_vm_setdata (gravity_vm *vm, void *data) {
vm->data = data;
}
void *gravity_vm_getdata (gravity_vm *vm) {
return vm->data;
}
void gravity_vm_set_callbacks (gravity_vm *vm, vm_transfer_cb vm_transfer, vm_cleanup_cb vm_cleanup) {
vm->transfer = vm_transfer;
vm->cleanup = vm_cleanup;
}
void gravity_vm_transfer (gravity_vm* vm, gravity_object_t *obj) {
if (vm->transfer) vm->transfer(vm, obj);
}
void gravity_vm_cleanup (gravity_vm* vm) {
if (vm->cleanup) vm->cleanup(vm);
}
void gravity_vm_filter (gravity_vm* vm, vm_filter_cb cleanup_filter) {
vm->filter = cleanup_filter;
}
bool gravity_vm_ismini (gravity_vm *vm) {
return (vm->context == NULL);
}
bool gravity_vm_isaborted (gravity_vm *vm) {
if (!vm) return true;
return vm->aborted;
}
void gravity_vm_setaborted (gravity_vm *vm) {
vm->aborted = true;
}
char *gravity_vm_anonymous (gravity_vm *vm) {
snprintf(vm->temp, sizeof(vm->temp), "%sanon%d", GRAVITY_VM_ANONYMOUS_PREFIX, ++vm->nanon);
return vm->temp;
}
void gravity_vm_memupdate (gravity_vm *vm, gravity_int_t value) {
vm->memallocated += value;
}
gravity_value_t gravity_vm_get (gravity_vm *vm, const char *key) {
if (key) {
if (strcmp(key, "gcenabled") == 0) return VALUE_FROM_BOOL(vm->gcenabled);
if (strcmp(key, "gcminthreshold") == 0) return VALUE_FROM_INT(vm->gcminthreshold);
if (strcmp(key, "gcthreshold") == 0) return VALUE_FROM_INT(vm->gcthreshold);
if (strcmp(key, "gcratio") == 0) return VALUE_FROM_FLOAT(vm->gcratio);
}
return VALUE_FROM_NULL;
}
bool gravity_vm_set (gravity_vm *vm, const char *key, gravity_value_t value) {
if (key) {
if ((strcmp(key, "gcenabled") == 0) && VALUE_ISA_BOOL(value)) {vm->gcenabled = VALUE_AS_BOOL(value); return true;}
if ((strcmp(key, "gcminthreshold") == 0) && VALUE_ISA_INT(value)) {vm->gcminthreshold = VALUE_AS_INT(value); return true;}
if ((strcmp(key, "gcthreshold") == 0) && VALUE_ISA_INT(value)) {vm->gcthreshold = VALUE_AS_INT(value); return true;}
if ((strcmp(key, "gcratio") == 0) && VALUE_ISA_FLOAT(value)) {vm->gcratio = VALUE_AS_FLOAT(value); return true;}
}
return false;
}
static bool real_set_superclass (gravity_vm *vm, gravity_class_t *c, gravity_value_t key, const char *supername) {
STATICVALUE_FROM_STRING(superkey, supername, strlen(supername));
void_r *stack = (void_r *)vm->data;
size_t n = marray_size(*stack);
for (size_t i=0; i<n; i++) {
gravity_object_t *obj = marray_get(*stack, i);
if (OBJECT_ISA_CLASS(obj)) {
gravity_class_t *c2 = (gravity_class_t *)gravity_class_lookup((gravity_class_t *)obj, superkey);
if ((c2) && (OBJECT_ISA_CLASS(c2))) {
mem_free(supername);
if (!gravity_class_setsuper(c, c2)) goto error_max_ivar;
return true;
}
}
else if (OBJECT_ISA_FUNCTION(obj)) {
gravity_function_t *f = (gravity_function_t *)obj;
if (f->tag == EXEC_TYPE_NATIVE) {
size_t count = marray_size(f->cpool);
for (size_t j=0; j<count; j++) {
gravity_value_t v = marray_get(f->cpool, j);
if (VALUE_ISA_CLASS(v)) {
gravity_class_t *c2 = VALUE_AS_CLASS(v);
if (strcmp(c2->identifier, supername) == 0) {
mem_free(supername);
if (!gravity_class_setsuper(c, c2)) goto error_max_ivar;
return true;
}
}
}
}
}
}
gravity_value_t v = gravity_vm_lookup(vm, superkey);
if (VALUE_ISA_CLASS(v)) {
mem_free(supername);
if (!gravity_class_setsuper(c, VALUE_AS_CLASS(v))) goto error_max_ivar;
return true;
}
report_runtime_error(vm, GRAVITY_ERROR_RUNTIME, "Unable to find superclass %s of class %s.", supername, VALUE_AS_CSTRING(key));
mem_free(supername);
return false;
error_max_ivar:
report_runtime_error(vm, GRAVITY_ERROR_RUNTIME, "Maximum number of allowed ivars (%d) reached for class %s.", MAX_IVARS, VALUE_AS_CSTRING(key));
return false;
}
static void vm_set_superclass_callback (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value, void *data) {
#pragma unused(hashtable)
gravity_vm *vm = (gravity_vm *)data;
if (VALUE_ISA_FUNCTION(value)) vm_set_superclass(vm, VALUE_AS_OBJECT(value));
if (!VALUE_ISA_CLASS(value)) return;
gravity_class_t *c = VALUE_AS_CLASS(value);
DEBUG_DESERIALIZE("set_superclass_callback for class %s", c->identifier);
const char *supername = c->xdata;
c->xdata = NULL;
if (supername) {
if (!real_set_superclass(vm, c, key, supername)) return;
}
gravity_hash_iterate(c->htable, vm_set_superclass_callback, vm);
}
static bool vm_set_superclass (gravity_vm *vm, gravity_object_t *obj) {
void_r *stack = (void_r *)vm->data;
marray_push(void*, *stack, obj);
if (OBJECT_ISA_CLASS(obj)) {
gravity_class_t *c = (gravity_class_t *)obj;
DEBUG_DESERIALIZE("set_superclass for class %s", c->identifier);
STATICVALUE_FROM_STRING(key, c->identifier, strlen(c->identifier));
const char *supername = c->xdata;
c->xdata = NULL;
if (supername) real_set_superclass(vm, c, key, supername);
gravity_hash_iterate(c->htable, vm_set_superclass_callback, vm);
} else if (OBJECT_ISA_FUNCTION(obj)) {
gravity_function_t *f = (gravity_function_t *)obj;
if (f->tag == EXEC_TYPE_NATIVE) {
size_t n = marray_size(f->cpool);
for (size_t i=0; i<n; i++) {
gravity_value_t v = marray_get(f->cpool, i);
if (VALUE_ISA_FUNCTION(v)) vm_set_superclass(vm, (gravity_object_t *)VALUE_AS_FUNCTION(v));
else if (VALUE_ISA_CLASS(v)) vm_set_superclass(vm, (gravity_object_t *)VALUE_AS_CLASS(v));
}
}
} else {
report_runtime_error(vm, GRAVITY_ERROR_RUNTIME, "%s", "Unable to recognize object type.");
return false;
}
marray_pop(*stack);
return true;
}
gravity_closure_t *gravity_vm_loadfile (gravity_vm *vm, const char *path) {
size_t len;
const char *buffer = file_read(path, &len);
if (!buffer) return NULL;
gravity_closure_t *closure = gravity_vm_loadbuffer(vm, buffer, len);
mem_free(buffer);
return closure;
}
gravity_closure_t *gravity_vm_loadbuffer (gravity_vm *vm, const char *buffer, size_t len) {
json_value *json = json_parse (buffer, len);
if (!json) goto abort_load;
if (json->type != json_object) goto abort_load;
void_r objects;
marray_init(objects);
gravity_gc_setenabled(vm, false);
gravity_closure_t *closure = NULL;
uint32_t n = json->u.object.length;
for (uint32_t i=0; i<n; ++i) {
json_value *entry = json->u.object.values[i].value;
if (entry->u.object.length == 0) continue;
if (entry->type != json_object) goto abort_load;
gravity_object_t *obj = NULL;
if (!gravity_object_deserialize(vm, entry, &obj)) goto abort_load;
if (!obj) continue;
marray_push(void*, objects, obj);
if (OBJECT_ISA_FUNCTION(obj)) {
gravity_function_t *f = (gravity_function_t *)obj;
const char *identifier = f->identifier;
gravity_closure_t *cl = gravity_closure_new(vm, f);
if (string_casencmp(identifier, INITMODULE_NAME, strlen(identifier)) == 0) {
closure = cl;
} else {
gravity_vm_setvalue(vm, identifier, VALUE_FROM_OBJECT(cl));
}
}
}
json_value_free(json);
size_t count = marray_size(objects);
if (count) {
void *saved = vm->data;
void_r stack;
marray_init(stack);
vm->data = (void *)&stack;
for (size_t i=0; i<count; ++i) {
gravity_object_t *obj = (gravity_object_t *)marray_get(objects, i);
if (!vm_set_superclass(vm, obj)) goto abort_generic;
}
marray_destroy(stack);
marray_destroy(objects);
vm->data = saved;
}
gravity_gc_setenabled(vm, true);
return closure;
abort_load:
report_runtime_error(vm, GRAVITY_ERROR_RUNTIME, "%s", "Unable to parse JSON executable file.");
if (json) json_value_free(json);
gravity_gc_setenabled(vm, true);
return NULL;
abort_generic:
if (json) json_value_free(json);
gravity_gc_setenabled(vm, true);
return NULL;
}
void gravity_gray_object (gravity_vm *vm, gravity_object_t *obj) {
if (!obj) return;
if (obj->gc.isdark) return;
DEBUG_GC("GRAY %s", gravity_object_debug(obj));
obj->gc.isdark = true;
marray_push(gravity_object_t *, vm->graylist, obj);
}
void gravity_gray_value (gravity_vm *vm, gravity_value_t v) {
if (gravity_value_isobject(v)) gravity_gray_object(vm, (gravity_object_t *)v.p);
}
static void gravity_gray_hash (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value, void *data) {
#pragma unused (hashtable)
gravity_vm *vm = (gravity_vm*)data;
gravity_gray_value(vm, key);
gravity_gray_value(vm, value);
}
static void gravity_gc_transform (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t *value, void *data) {
#pragma unused (hashtable)
gravity_vm *vm = (gravity_vm *)data;
gravity_object_t *obj = VALUE_AS_OBJECT(*value);
if (OBJECT_ISA_FUNCTION(obj)) {
gravity_function_t *f = (gravity_function_t *)obj;
if (f->tag == EXEC_TYPE_SPECIAL) {
if (f->special[0]) {
gravity_gc_transfer(vm, (gravity_object_t *)f->special[0]);
f->special[0] = gravity_closure_new(vm, f->special[0]);
}
if (f->special[1]) {
gravity_gc_transfer(vm, (gravity_object_t *)f->special[1]);
f->special[1] = gravity_closure_new(vm, f->special[1]);
}
} else if (f->tag == EXEC_TYPE_NATIVE) {
gravity_vm_initmodule(vm, f);
}
bool is_super_function = false;
if (VALUE_ISA_STRING(key)) {
gravity_string_t *s = VALUE_AS_STRING(key);
is_super_function = ((s->len > 5) && (string_casencmp(s->s, CLASS_INTERNAL_INIT_NAME, 5) == 0));
}
gravity_closure_t *closure = gravity_closure_new(vm, f);
*value = VALUE_FROM_OBJECT((gravity_object_t *)closure);
if (!is_super_function) gravity_gc_transfer(vm, obj);
} else if (OBJECT_ISA_CLASS(obj)) {
gravity_class_t *c = (gravity_class_t *)obj;
gravity_vm_loadclass(vm, c);
} else {
assert(0);
}
}
void gravity_vm_initmodule (gravity_vm *vm, gravity_function_t *f) {
size_t n = marray_size(f->cpool);
for (size_t i=0; i<n; i++) {
gravity_value_t v = marray_get(f->cpool, i);
if (VALUE_ISA_CLASS(v)) {
gravity_class_t *c = VALUE_AS_CLASS(v);
gravity_vm_loadclass(vm, c);
}
else if (VALUE_ISA_FUNCTION(v)) {
gravity_vm_initmodule(vm, VALUE_AS_FUNCTION(v));
}
}
}
static void gravity_gc_transfer_object (gravity_vm *vm, gravity_object_t *obj) {
DEBUG_GC("GC TRANSFER %s", gravity_object_debug(obj));
++vm->gccount;
obj->gc.next = vm->gchead;
vm->gchead = obj;
}
static void gravity_gc_transfer (gravity_vm *vm, gravity_object_t *obj) {
if (vm->gcenabled) {
#if GRAVITY_GC_STRESSTEST
gravity_object_t **ptr = &vm->gchead;
while (*ptr) {
if (obj == *ptr) {
printf("Object %s already GC!\n", gravity_object_debug(obj));
assert(0);
}
ptr = &(*ptr)->gc.next;
}
gravity_gc_start(vm);
#else
if (vm->memallocated >= vm->gcthreshold) gravity_gc_start(vm);
#endif
}
gravity_gc_transfer_object(vm, obj);
}
static void gravity_gc_sweep (gravity_vm *vm) {
gravity_object_t **obj = &vm->gchead;
while (*obj) {
if (!(*obj)->gc.isdark) {
gravity_object_t *unreached = *obj;
*obj = unreached->gc.next;
gravity_object_free(vm, unreached);
--vm->gccount;
} else {
(*obj)->gc.isdark = false;
obj = &(*obj)->gc.next;
}
}
}
void gravity_gc_start (gravity_vm *vm) {
if (!vm->fiber) return;
#if GRAVITY_GC_STATS
gravity_int_t membefore = vm->memallocated;
nanotime_t tstart = nanotime();
#endif
vm->memallocated = 0;
for (uint32_t i=0; i<marray_size(vm->gcsave); ++i) {
gravity_object_t *obj = marray_get(vm->gcsave, i);
gravity_gray_object(vm, obj);
}
gravity_gray_object(vm, (gravity_object_t *)vm->fiber);
gravity_hash_iterate(vm->context, gravity_gray_hash, (void*)vm);
while (marray_size(vm->graylist)) {
gravity_object_t *obj = marray_pop(vm->graylist);
gravity_object_blacken(vm, obj);
}
gravity_gc_sweep(vm);
vm->gcthreshold = vm->memallocated + (vm->memallocated * vm->gcratio / 100);
if (vm->gcthreshold < vm->gcminthreshold) vm->gcthreshold = vm->gcminthreshold;
#if GRAVITY_GC_STATS
nanotime_t tend = nanotime();
double gctime = millitime(tstart, tend);
printf("GC %lu before, %lu after (%lu collected - %lu objects), next at %lu. Took %.3fs.\n",
(unsigned long)membefore,
(unsigned long)vm->memallocated,
(membefore > vm->memallocated) ? (unsigned long)(membefore - vm->memallocated) : 0,
(unsigned long)vm->gccount,
(unsigned long)vm->gcthreshold,
gctime);
#endif
}
static void gravity_gc_cleanup (gravity_vm *vm) {
if (!vm->gchead) return;
if (vm->filter) {
vm_filter_cb filter = vm->filter;
gravity_object_t *obj = vm->gchead;
gravity_object_t *prev = NULL;
while (obj) {
if (!filter(obj)) {
prev = obj;
obj = obj->gc.next;
continue;
}
gravity_object_t *next = obj->gc.next;
if (!prev) vm->gchead = next;
else prev->gc.next = next;
gravity_object_free(vm, obj);
--vm->gccount;
obj = next;
}
return;
}
gravity_object_t *obj = vm->gchead;
while (obj) {
gravity_object_t *next = obj->gc.next;
gravity_object_free(vm, obj);
--vm->gccount;
obj = next;
}
vm->gchead = NULL;
while (marray_size(vm->gcsave)) {
gravity_object_t *tobj = marray_pop(vm->gcsave);
gravity_object_free(vm, tobj);
}
}
void gravity_gc_setenabled (gravity_vm *vm, bool enabled) {
vm->gcenabled = enabled;
}
void gravity_gc_push (gravity_vm *vm, gravity_object_t *obj) {
marray_push(gravity_object_t *, vm->gcsave, obj);
}
void gravity_gc_pop (gravity_vm *vm) {
marray_pop(vm->gcsave);
}