summaryrefslogtreecommitdiffhomepage
path: root/vm.c
diff options
context:
space:
mode:
Diffstat (limited to 'vm.c')
-rw-r--r--vm.c2233
1 files changed, 2233 insertions, 0 deletions
diff --git a/vm.c b/vm.c
new file mode 100644
index 0000000..e5cfdb5
--- /dev/null
+++ b/vm.c
@@ -0,0 +1,2233 @@
+/*
+ * Copyright (C) 2020-2021 Jo-Philipp Wich <jo@mein.io>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "vm.h"
+#include "compiler.h"
+#include "lib.h" /* format_error_context() */
+
+#undef __insn
+#define __insn(_name) #_name,
+
+static const char *insn_names[__I_MAX] = {
+ __insns
+};
+
+static const uc_insn_definition insn_defs[__I_MAX] = {
+ [I_NOOP] = { 0, 0, 0 },
+
+ [I_LOAD] = { 0, 1, 4 },
+ [I_LOAD8] = { 0, 1, -1 },
+ [I_LOAD16] = { 0, 1, -2 },
+ [I_LOAD32] = { 0, 1, -4 },
+
+ [I_LREXP] = { 0, 1, 4 },
+ [I_LNULL] = { 0, 1, 0 },
+ [I_LTRUE] = { 0, 1, 0 },
+ [I_LFALSE] = { 0, 1, 0 },
+ [I_LTHIS] = { 0, 1, 0 },
+
+ [I_LLOC] = { 0, 1, 4 },
+ [I_LVAR] = { 0, 1, 4 },
+ [I_LUPV] = { 0, 1, 4 },
+ [I_LVAL] = { 2, 1, 0 },
+
+ [I_CLFN] = { 0, 1, 4, true },
+ [I_ARFN] = { 0, 1, 4, true },
+
+ [I_SLOC] = { 0, 0, 4 },
+ [I_SUPV] = { 0, 0, 4 },
+ [I_SVAR] = { 0, 0, 4 },
+ [I_SVAL] = { 3, 1, 0 },
+
+ [I_ULOC] = { 1, 0, 4 },
+ [I_UUPV] = { 1, 0, 4 },
+ [I_UVAR] = { 1, 0, 4 },
+ [I_UVAL] = { 3, 1, 1 },
+
+ [I_NARR] = { 0, 1, 4 },
+ [I_PARR] = { -1, 0, 4 },
+ [I_MARR] = { 1, 0, 0 },
+
+ [I_NOBJ] = { 0, 1, 4 },
+ [I_SOBJ] = { -1, 0, 4 },
+ [I_MOBJ] = { 1, 0, 0 },
+
+ [I_ADD] = { 2, 1, 0 },
+ [I_SUB] = { 2, 1, 0 },
+ [I_MUL] = { 2, 1, 0 },
+ [I_DIV] = { 2, 1, 0 },
+ [I_MOD] = { 2, 1, 0 },
+ [I_LSHIFT] = { 2, 1, 0 },
+ [I_RSHIFT] = { 2, 1, 0 },
+ [I_BAND] = { 2, 1, 0 },
+ [I_BXOR] = { 2, 1, 0 },
+ [I_BOR] = { 2, 1, 0 },
+ [I_EQ] = { 2, 1, 0 },
+ [I_NE] = { 2, 1, 0 },
+ [I_EQS] = { 2, 1, 0 },
+ [I_NES] = { 2, 1, 0 },
+ [I_LT] = { 2, 1, 0 },
+ [I_GT] = { 2, 1, 0 },
+ [I_IN] = { 2, 1, 0 },
+
+ [I_JMP] = { 0, 0, -4, true },
+ [I_JMPZ] = { 1, 0, -4, true },
+
+ [I_COPY] = { 0, 1, 1 },
+ [I_POP] = { 1, 0, 0 },
+ [I_CUPV] = { 1, 0, 0 },
+
+ [I_PLUS] = { 1, 1, 0 },
+ [I_MINUS] = { 1, 1, 0 },
+
+ [I_RETURN] = { 1, 0, 0 },
+ [I_CALL] = { -2, 1, 4 },
+ [I_MCALL] = { -3, 1, 4 },
+
+ [I_NEXTK] = { 2, 2, 0 },
+ [I_NEXTKV] = { 2, 3, 0 },
+
+ [I_PRINT] = { 1, 0, 0 }
+};
+
+static const char *exception_type_strings[] = {
+ [EXCEPTION_SYNTAX] = "Syntax error",
+ [EXCEPTION_RUNTIME] = "Runtime error",
+ [EXCEPTION_TYPE] = "Type error",
+ [EXCEPTION_REFERENCE] = "Reference error",
+ [EXCEPTION_USER] = "Error",
+};
+
+
+static void
+uc_vm_reset_stack(uc_vm *vm)
+{
+ while (vm->stack.count > 0) {
+ vm->stack.count--;
+ uc_value_put(vm->stack.entries[vm->stack.count]);
+ vm->stack.entries[vm->stack.count] = NULL;
+ }
+}
+
+static json_object *
+uc_vm_callframe_pop(uc_vm *vm);
+
+static void
+uc_vm_reset_callframes(uc_vm *vm)
+{
+ while (vm->callframes.count > 0)
+ uc_value_put(uc_vm_callframe_pop(vm));
+}
+
+void uc_vm_init(uc_vm *vm, uc_parse_config *config)
+{
+ char *s = getenv("TRACE");
+
+ vm->exception.type = EXCEPTION_NONE;
+ vm->exception.message = NULL;
+
+ vm->trace = s ? strtoul(s, NULL, 0) : 0;
+
+ vm->config = config;
+
+ vm->open_upvals = NULL;
+
+ uc_vm_reset_stack(vm);
+}
+
+void uc_vm_free(uc_vm *vm)
+{
+ uc_upvalref *ref;
+
+ uc_value_put(vm->exception.stacktrace);
+ free(vm->exception.message);
+
+ while (vm->open_upvals) {
+ ref = vm->open_upvals->next;
+ uc_value_put(vm->open_upvals->header.jso);
+ vm->open_upvals = ref;
+ }
+
+ uc_vm_reset_callframes(vm);
+ uc_vm_reset_stack(vm);
+ uc_vector_clear(&vm->stack);
+ uc_vector_clear(&vm->callframes);
+}
+
+static uc_chunk *
+uc_vm_frame_chunk(uc_callframe *frame)
+{
+ return frame->closure ? &frame->closure->function->chunk : NULL;
+}
+
+static uc_callframe *
+uc_vm_current_frame(uc_vm *vm)
+{
+ return uc_vector_last(&vm->callframes);
+}
+
+static uc_chunk *
+uc_vm_current_chunk(uc_vm *vm)
+{
+ return uc_vm_frame_chunk(uc_vm_current_frame(vm));
+}
+
+static enum insn_type
+uc_vm_decode_insn(uc_vm *vm, uc_callframe *frame, uc_chunk *chunk)
+{
+ enum insn_type insn;
+
+#ifndef NDEBUG
+ uint8_t *end = chunk->entries + chunk->count;
+#endif
+
+ assert(frame->ip < end);
+
+ insn = frame->ip[0];
+ frame->ip++;
+
+ assert(frame->ip + abs(insn_defs[insn].operand_bytes) <= end);
+
+ switch (insn_defs[insn].operand_bytes) {
+ case 0:
+ break;
+
+ case -1:
+ vm->arg.s8 = frame->ip[0] - 0x7f;
+ frame->ip++;
+ break;
+
+ case -2:
+ vm->arg.s16 = (
+ frame->ip[0] * 0x100 +
+ frame->ip[1]
+ ) - 0x7fff;
+ frame->ip += 2;
+ break;
+
+ case -4:
+ vm->arg.s32 = (
+ frame->ip[0] * 0x1000000 +
+ frame->ip[1] * 0x10000 +
+ frame->ip[2] * 0x100 +
+ frame->ip[3]
+ ) - 0x7fffffff;
+ frame->ip += 4;
+ break;
+
+ case 1:
+ vm->arg.u8 = frame->ip[0];
+ frame->ip++;
+ break;
+
+ case 4:
+ vm->arg.u32 = (
+ frame->ip[0] * 0x1000000 +
+ frame->ip[1] * 0x10000 +
+ frame->ip[2] * 0x100 +
+ frame->ip[3]
+ );
+ frame->ip += 4;
+ break;
+
+ default:
+ fprintf(stderr, "Unhandled operand format: %d\n", insn_defs[insn].operand_bytes);
+ abort();
+ }
+
+ return insn;
+}
+
+
+static void
+uc_vm_frame_dump(uc_vm *vm, uc_callframe *frame)
+{
+ uc_chunk *chunk = uc_vm_frame_chunk(frame);
+ uc_function *function;
+ uc_closure *closure;
+ uc_upvalref *ref;
+ size_t i;
+
+ fprintf(stderr, " [*] CALLFRAME[%lx]\n",
+ frame - vm->callframes.entries);
+
+ fprintf(stderr, " |- stackframe %zu/%zu\n",
+ frame->stackframe, vm->stack.count);
+
+ fprintf(stderr, " |- ctx %s\n",
+ json_object_to_json_string(frame->ctx));
+
+ if (chunk) {
+ fprintf(stderr, " |- %zu constants\n",
+ chunk->constants.isize);
+
+ for (i = 0; i < chunk->constants.isize; i++)
+ fprintf(stderr, " | [%zu] %s\n",
+ i,
+ json_object_to_json_string(uc_chunk_get_constant(chunk, i)));
+
+ closure = frame->closure;
+ function = closure->function;
+
+ fprintf(stderr, " `- %zu upvalues\n",
+ function->nupvals);
+
+ for (i = 0; i < function->nupvals; i++) {
+ ref = closure->upvals[i];
+
+ if (ref->closed)
+ fprintf(stderr, " [%zu] <%p> %s {closed} %s\n",
+ i,
+ ref,
+ json_object_to_json_string(
+ uc_chunk_debug_get_variable(chunk, 0, i, true)),
+ json_object_to_json_string(ref->value));
+ else
+ fprintf(stderr, " [%zu] <%p> %s {open[%zu]} %s\n",
+ i,
+ ref,
+ json_object_to_json_string(
+ uc_chunk_debug_get_variable(chunk, 0, i, true)),
+ ref->slot,
+ json_object_to_json_string(vm->stack.entries[ref->slot]));
+ }
+ }
+}
+
+void
+uc_vm_stack_push(uc_vm *vm, json_object *value)
+{
+ uc_vector_grow(&vm->stack);
+
+ uc_value_put(vm->stack.entries[vm->stack.count]);
+
+ vm->stack.entries[vm->stack.count] = value;
+ vm->stack.count++;
+
+ if (vm->trace)
+ fprintf(stderr, " [+%zd] %s\n",
+ vm->stack.count,
+ json_object_to_json_string(value));
+}
+
+json_object *
+uc_vm_stack_pop(uc_vm *vm)
+{
+ json_object *rv;
+
+ vm->stack.count--;
+ rv = vm->stack.entries[vm->stack.count];
+ vm->stack.entries[vm->stack.count] = NULL;
+
+ if (vm->trace)
+ fprintf(stderr, " [-%zd] %s\n",
+ vm->stack.count + 1,
+ json_object_to_json_string(rv));
+
+ return rv;
+}
+
+json_object *
+uc_vm_stack_peek(uc_vm *vm, size_t offset)
+{
+ return vm->stack.entries[vm->stack.count + (-1 - offset)];
+}
+
+static void
+uc_vm_stack_set(uc_vm *vm, size_t offset, json_object *value)
+{
+ if (vm->trace)
+ fprintf(stderr, " [!%zu] %s\n",
+ offset, json_object_to_json_string(value));
+
+ uc_value_put(vm->stack.entries[offset]);
+ vm->stack.entries[offset] = value;
+}
+
+static void
+uc_vm_call_native(uc_vm *vm, json_object *ctx, uc_cfunction *fptr, bool mcall, size_t nargs)
+{
+ json_object *res = NULL;
+ uc_callframe *frame;
+
+ /* add new callframe */
+ uc_vector_grow(&vm->callframes);
+
+ frame = &vm->callframes.entries[vm->callframes.count++];
+ frame->stackframe = vm->stack.count - nargs - 1;
+ frame->cfunction = fptr;
+ frame->closure = NULL;
+ frame->ctx = ctx;
+ frame->mcall = mcall;
+
+ if (vm->trace)
+ uc_vm_frame_dump(vm, frame);
+
+ res = fptr->cfn(vm, nargs);
+
+ /* reset stack */
+ uc_value_put(uc_vm_callframe_pop(vm));
+
+ /* push return value */
+ if (!vm->exception.type)
+ uc_vm_stack_push(vm, res);
+ else
+ uc_value_put(res);
+}
+
+static bool
+uc_vm_call_function(uc_vm *vm, json_object *ctx, json_object *fno, bool mcall, size_t argspec)
+{
+ size_t i, j, stackoff, nargs = argspec & 0xffff, nspreads = argspec >> 16;
+ uc_callframe *frame = uc_vm_current_frame(vm);
+ json_object *ellip, *arg;
+ uc_function *function;
+ uc_closure *closure;
+ uint16_t slot, tmp;
+
+ /* XXX: make dependent on stack size */
+ if (vm->callframes.count >= 1000) {
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Too much recursion");
+
+ return false;
+ }
+
+ stackoff = vm->stack.count - nargs - 1;
+
+ /* argument list contains spread operations, we need to reshuffle the stack */
+ if (nspreads > 0) {
+ /* create temporary array */
+ ellip = xjs_new_array_size(nargs);
+
+ /* pop original stack values and push to temp array in reverse order */
+ for (i = 0; i < nargs; i++)
+ json_object_array_add(ellip, uc_vm_stack_pop(vm));
+
+ /* for each spread value index ... */
+ for (i = 0, slot = nargs; i < nspreads; i++) {
+ /* decode stack depth value */
+ tmp = frame->ip[0] * 0x100 + frame->ip[1];
+ frame->ip += 2;
+
+ /* push each preceeding non-spread value to the stack */
+ for (j = slot; j > tmp + 1; j--)
+ uc_vm_stack_push(vm, uc_value_get(json_object_array_get_idx(ellip, j - 1)));
+
+ /* read spread value at index... */
+ slot = tmp;
+ arg = uc_value_get(json_object_array_get_idx(ellip, slot));
+
+ /* ... ensure that it is an array type ... */
+ if (!json_object_is_type(arg, json_type_array)) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE,
+ "(%s) is not iterable",
+ json_object_to_json_string(arg));
+
+ return false;
+ }
+
+ /* ... and push each spread array value as argument to the stack */
+ for (j = 0; j < json_object_array_length(arg); j++)
+ uc_vm_stack_push(vm, uc_value_get(json_object_array_get_idx(arg, j)));
+
+ uc_value_put(arg);
+ }
+
+ /* push remaining non-spread arguments to the stack */
+ for (i = slot; i > 0; i--)
+ uc_vm_stack_push(vm, uc_value_get(json_object_array_get_idx(ellip, i - 1)));
+
+ /* free temp array */
+ uc_value_put(ellip);
+
+ /* update arg count */
+ nargs = vm->stack.count - stackoff - 1;
+ }
+
+ /* is a native function */
+ if (uc_object_is_type(fno, UC_OBJ_CFUNCTION)) {
+ uc_vm_call_native(vm, ctx, uc_object_as_cfunction(fno), mcall, nargs);
+
+ return true;
+ }
+
+ if (!uc_object_is_type(fno, UC_OBJ_CLOSURE)) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "left-hand side is not a function");
+
+ return false;
+ }
+
+ closure = uc_object_as_closure(fno);
+ function = closure->function;
+
+ /* fewer arguments on stack than function expects => pad */
+ if (nargs < function->nargs) {
+ for (i = nargs; i < function->nargs; i++) {
+ if (function->vararg && (i + 1) == function->nargs)
+ uc_vm_stack_push(vm, xjs_new_array_size(0));
+ else
+ uc_vm_stack_push(vm, NULL);
+ }
+ }
+
+ /* more arguments on stack than function expects... */
+ else if (nargs > function->nargs - function->vararg) {
+ /* is a vararg function => pass excess args as array */
+ if (function->vararg) {
+ ellip = xjs_new_array_size(nargs - (function->nargs - 1));
+
+ for (i = function->nargs; i <= nargs; i++)
+ json_object_array_add(ellip, uc_vm_stack_peek(vm, nargs - i));
+
+ for (i = function->nargs; i <= nargs; i++)
+ uc_vm_stack_pop(vm);
+
+ uc_vm_stack_push(vm, ellip);
+ }
+
+ /* static amount of args => drop excess values */
+ else {
+ for (i = function->nargs; i < nargs; i++)
+ uc_value_put(uc_vm_stack_pop(vm));
+ }
+ }
+
+ uc_vector_grow(&vm->callframes);
+
+ frame = &vm->callframes.entries[vm->callframes.count++];
+ frame->stackframe = stackoff;
+ frame->cfunction = NULL;
+ frame->closure = closure;
+ frame->ctx = ctx;
+ frame->ip = function->chunk.entries;
+ frame->mcall = mcall;
+
+ if (vm->trace)
+ uc_vm_frame_dump(vm, frame);
+
+ return true;
+}
+
+static uc_source *last_source = NULL;
+static size_t last_srcpos = 0;
+
+static void
+uc_dump_insn(uc_vm *vm, uint8_t *pos, enum insn_type insn)
+{
+ uc_callframe *frame = uc_vm_current_frame(vm);
+ uc_chunk *chunk = uc_vm_frame_chunk(frame);
+ size_t msglen = 0, srcpos;
+ json_object *cnst = NULL;
+ char *msg = NULL;
+
+ srcpos = uc_function_get_srcpos(frame->closure->function, pos - chunk->entries);
+
+ if (last_srcpos == 0 || last_source != frame->closure->function->source || srcpos != last_srcpos) {
+ format_source_context(&msg, &msglen,
+ frame->closure->function->source,
+ srcpos, true);
+
+ fprintf(stderr, "%s", msg);
+
+ last_source = frame->closure->function->source;
+ last_srcpos = srcpos;
+ }
+
+ fprintf(stderr, "%08lx %s", pos - chunk->entries, insn_names[insn]);
+
+ switch (insn_defs[insn].operand_bytes) {
+ case 0:
+ break;
+
+ case -1:
+ fprintf(stderr, " {%s%hhd}", vm->arg.s8 < 0 ? "" : "+", vm->arg.s8);
+ break;
+
+ case -2:
+ fprintf(stderr, " {%s%hx}", vm->arg.s16 < 0 ? "" : "+", vm->arg.s16);
+ break;
+
+ case -4:
+ fprintf(stderr, " {%s%x}", vm->arg.s32 < 0 ? "" : "+", vm->arg.s32);
+ break;
+
+ case 1:
+ fprintf(stderr, " {%hhu}", vm->arg.u8);
+ break;
+
+ case 2:
+ fprintf(stderr, " {%hx}", vm->arg.u16);
+ break;
+
+ case 4:
+ fprintf(stderr, " {%x}", vm->arg.u32);
+ break;
+
+ default:
+ fprintf(stderr, " (unknown operand format: %d)", insn_defs[insn].operand_bytes);
+ break;
+ }
+
+ switch (insn) {
+ case I_LOAD:
+ case I_LVAR:
+ case I_SVAR:
+ cnst = uc_chunk_get_constant(uc_vm_frame_chunk(uc_vector_last(&vm->callframes)), vm->arg.u32);
+
+ fprintf(stderr, "\t; %s", cnst ? json_object_to_json_string(cnst) : "null");
+ uc_value_put(cnst);
+ break;
+
+ case I_LLOC:
+ case I_LUPV:
+ case I_SLOC:
+ case I_SUPV:
+ cnst = uc_chunk_debug_get_variable(chunk, pos - chunk->entries, vm->arg.u32, (insn == I_LUPV || insn == I_SUPV));
+
+ fprintf(stderr, "\t; %s", cnst ? json_object_to_json_string(cnst) : "(?)");
+ uc_value_put(cnst);
+ break;
+
+ case I_ULOC:
+ case I_UUPV:
+ cnst = uc_chunk_debug_get_variable(chunk, pos - chunk->entries, vm->arg.u32 & 0x00ffffff, (insn == I_UUPV));
+ /* fall through */
+
+ case I_UVAR:
+ if (!cnst)
+ cnst = uc_chunk_get_constant(uc_vm_frame_chunk(uc_vector_last(&vm->callframes)), vm->arg.u32 & 0x00ffffff);
+
+ fprintf(stderr, "\t; %s (%s)",
+ cnst ? json_object_to_json_string(cnst) : "(?)",
+ insn_names[vm->arg.u32 >> 24]);
+
+ uc_value_put(cnst);
+ break;
+
+ case I_UVAL:
+ fprintf(stderr, "\t; (%s)", insn_names[vm->arg.u32]);
+ break;
+
+ default:
+ break;
+ }
+
+ fprintf(stderr, "\n");
+}
+
+static int
+uc_vm_exception_tostring(json_object *jso, struct printbuf *pb, int level, int flags)
+{
+ bool strict = (level > 0) || (flags & JSON_C_TO_STRING_STRICT);
+ json_object *message = json_object_object_get(jso, "message");
+
+ return sprintbuf(pb, "%s",
+ strict ? json_object_to_json_string(message) : json_object_get_string(message));
+}
+
+static bool
+uc_vm_handle_exception(uc_vm *vm)
+{
+ uc_callframe *frame = uc_vm_current_frame(vm);
+ uc_chunk *chunk = NULL;
+ json_object *exo;
+ size_t i, pos;
+
+ if (!frame->closure)
+ return false;
+
+ chunk = uc_vm_frame_chunk(frame);
+ pos = frame->ip - chunk->entries;
+
+ /* iterate the known exception ranges, see if the current ip falls into any of them */
+ for (i = 0; i < chunk->ehranges.count; i++) {
+ /* skip nonmatching ranges */
+ if (pos < chunk->ehranges.entries[i].from ||
+ pos >= chunk->ehranges.entries[i].to)
+ continue;
+
+ /* we found a matching range... first unwind stack */
+ while (vm->stack.count > frame->stackframe + chunk->ehranges.entries[i].slot)
+ uc_value_put(uc_vm_stack_pop(vm));
+
+ /* prepare exception object and expose it to user handler code */
+ exo = xjs_new_object();
+
+ json_object_object_add(exo, "type", xjs_new_string(exception_type_strings[vm->exception.type]));
+ json_object_object_add(exo, "message", xjs_new_string(vm->exception.message));
+ json_object_object_add(exo, "stacktrace", uc_value_get(vm->exception.stacktrace));
+
+ json_object_set_serializer(exo, uc_vm_exception_tostring, NULL, NULL);
+ uc_vm_stack_push(vm, exo);
+
+ /* reset exception information */
+ free(vm->exception.message);
+
+ vm->exception.type = EXCEPTION_NONE;
+ vm->exception.message = NULL;
+
+ /* jump to exception handler */
+ if (chunk->ehranges.entries[i].target >= chunk->count) {
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "jump target out of range");
+ return false;
+ }
+
+#if 0
+ if (vm->trace && chunk->entries + chunk->ehranges.entries[i].target > frame->ip) {
+ while (frame->ip < chunk->entries + chunk->ehranges.entries[i].target) {
+ fprintf(stderr, "(eh:skip) [%p:%zu] ", chunk, frame->ip - chunk->entries);
+ uc_dump_insn(vm, frame->ip, uc_vm_decode_insn(vm, frame, chunk));
+ }
+ }
+#endif
+
+ frame->ip = chunk->entries + chunk->ehranges.entries[i].target;
+
+ return true;
+ }
+
+ return false;
+}
+
+static json_object *
+uc_vm_capture_stacktrace(uc_vm *vm, size_t i)
+{
+ json_object *stacktrace, *entry, *last = NULL;
+ uc_function *function;
+ uc_callframe *frame;
+ size_t off, srcpos;
+ char *name;
+
+ stacktrace = xjs_new_array();
+
+ for (; i > 0; i--) {
+ frame = &vm->callframes.entries[i - 1];
+ entry = xjs_new_object();
+
+ if (frame->closure) {
+ function = frame->closure->function;
+
+ off = (frame->ip - uc_vm_frame_chunk(frame)->entries) - 1;
+ srcpos = uc_function_get_srcpos(function, off);
+
+ json_object_object_add(entry, "filename", xjs_new_string(function->source->filename));
+ json_object_object_add(entry, "line", xjs_new_int64(uc_source_get_line(function->source, &srcpos)));
+ json_object_object_add(entry, "byte", xjs_new_int64(srcpos));
+ }
+
+ if (i > 1) {
+ if (frame->closure) {
+ if (frame->closure->function->name)
+ name = frame->closure->function->name;
+ else if (frame->closure->is_arrow)
+ name = "[arrow function]";
+ else
+ name = "[anonymous function]";
+ }
+ else {
+ name = frame->cfunction->name;
+ }
+
+ json_object_object_add(entry, "function", xjs_new_string(name));
+ }
+
+ if (!json_object_equal(last, entry)) {
+ json_object_array_add(stacktrace, entry);
+ last = entry;
+ }
+ else {
+ uc_value_put(entry);
+ }
+ }
+
+ return stacktrace;
+}
+
+static json_object *
+uc_vm_get_error_context(uc_vm *vm)
+{
+ json_object *stacktrace;
+ uc_callframe *frame;
+ uc_chunk *chunk;
+ size_t offset, len = 0, i;
+ char *msg = NULL;
+
+ /* skip to first non-native function call frame */
+ for (i = vm->callframes.count; i > 0; i--)
+ if (vm->callframes.entries[i - 1].closure)
+ break;
+
+ frame = &vm->callframes.entries[i - 1];
+
+ if (!frame->closure)
+ return NULL;
+
+ chunk = uc_vm_frame_chunk(frame);
+ offset = uc_function_get_srcpos(frame->closure->function, (frame->ip - chunk->entries) - 1);
+ stacktrace = uc_vm_capture_stacktrace(vm, i);
+
+ if (offset)
+ format_error_context(&msg, &len, frame->closure->function->source, stacktrace, offset);
+ else
+ xasprintf(&msg, "At offset %zu", (frame->ip - chunk->entries) - 1);
+
+ json_object_object_add(json_object_array_get_idx(stacktrace, 0), "context", xjs_new_string(msg));
+
+ free(msg);
+
+ return stacktrace;
+}
+
+void __attribute__((format(printf, 3, 0)))
+uc_vm_raise_exception(uc_vm *vm, uc_exception_type_t type, const char *fmt, ...)
+{
+ va_list ap;
+
+ vm->exception.type = type;
+
+ free(vm->exception.message);
+
+ va_start(ap, fmt);
+ xvasprintf(&vm->exception.message, fmt, ap);
+ va_end(ap);
+
+ uc_value_put(vm->exception.stacktrace);
+ vm->exception.stacktrace = uc_vm_get_error_context(vm);
+}
+
+
+static void
+uc_vm_insn_load(uc_vm *vm, enum insn_type insn)
+{
+ switch (insn) {
+ case I_LOAD:
+ uc_vm_stack_push(vm, uc_chunk_get_constant(uc_vm_current_chunk(vm), vm->arg.u32));
+ break;
+
+ case I_LOAD8:
+ uc_vm_stack_push(vm, xjs_new_int64(vm->arg.s8));
+ break;
+
+ case I_LOAD16:
+ uc_vm_stack_push(vm, xjs_new_int64(vm->arg.s16));
+ break;
+
+ case I_LOAD32:
+ uc_vm_stack_push(vm, xjs_new_int64(vm->arg.s32));
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+uc_vm_insn_load_regexp(uc_vm *vm, enum insn_type insn)
+{
+ bool icase = false, newline = false, global = false;
+ json_object *jstr = uc_chunk_get_constant(uc_vm_current_chunk(vm), vm->arg.u32);
+ const char *str;
+ uc_regexp *re;
+ char *err;
+
+ if (!json_object_is_type(jstr, json_type_string) || json_object_get_string_len(jstr) < 2) {
+ uc_vm_stack_push(vm, NULL);
+ uc_value_put(jstr);
+
+ return;
+ }
+
+ str = json_object_get_string(jstr);
+
+ global = (*str & (1 << 0));
+ icase = (*str & (1 << 1));
+ newline = (*str & (1 << 2));
+
+ re = uc_regexp_new(++str, icase, newline, global, &err);
+
+ uc_value_put(jstr);
+
+ if (re)
+ uc_vm_stack_push(vm, re->header.jso);
+ else
+ uc_vm_raise_exception(vm, EXCEPTION_SYNTAX, "%s", err);
+}
+
+static void
+uc_vm_insn_load_null(uc_vm *vm, enum insn_type insn)
+{
+ uc_vm_stack_push(vm, NULL);
+}
+
+static void
+uc_vm_insn_load_bool(uc_vm *vm, enum insn_type insn)
+{
+ uc_vm_stack_push(vm, xjs_new_boolean(insn == I_LTRUE));
+}
+
+static void
+uc_vm_insn_load_var(uc_vm *vm, enum insn_type insn)
+{
+ json_object *name, *val = NULL;
+ uc_prototype *scope, *next;
+
+ scope = vm->globals;
+ name = uc_chunk_get_constant(uc_vm_current_chunk(vm), vm->arg.u32);
+
+ while (json_object_get_type(name) == json_type_string) {
+ if (json_object_object_get_ex(scope->header.jso, json_object_get_string(name), &val))
+ break;
+
+ next = scope->parent;
+
+ if (!next) {
+ if (vm->config->strict_declarations) {
+ uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
+ "access to undeclared variable %s",
+ json_object_get_string(name));
+ }
+
+ break;
+ }
+
+ scope = next;
+ }
+
+ uc_value_put(name);
+
+ uc_vm_stack_push(vm, uc_value_get(val));
+}
+
+static void
+uc_vm_insn_load_val(uc_vm *vm, enum insn_type insn)
+{
+ json_object *k = uc_vm_stack_pop(vm);
+ json_object *v = uc_vm_stack_pop(vm);
+
+ switch (json_object_get_type(v)) {
+ case json_type_object:
+ case json_type_array:
+ uc_vm_stack_push(vm, uc_getval(v, k));
+ break;
+
+ default:
+ uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
+ "left-hand side expression is %s",
+ v ? "not an array or object" : "null");
+
+ break;
+ }
+
+
+ uc_value_put(k);
+ uc_value_put(v);
+}
+
+static void
+uc_vm_insn_load_upval(uc_vm *vm, enum insn_type insn)
+{
+ uc_callframe *frame = uc_vm_current_frame(vm);
+ uc_upvalref *ref = frame->closure->upvals[vm->arg.u32];
+
+ if (ref->closed)
+ uc_vm_stack_push(vm, uc_value_get(ref->value));
+ else
+ uc_vm_stack_push(vm, uc_value_get(vm->stack.entries[ref->slot]));
+}
+
+static void
+uc_vm_insn_load_local(uc_vm *vm, enum insn_type insn)
+{
+ uc_callframe *frame = uc_vm_current_frame(vm);
+
+ uc_vm_stack_push(vm, uc_value_get(vm->stack.entries[frame->stackframe + vm->arg.u32]));
+}
+
+static uc_upvalref *
+uc_vm_capture_upval(uc_vm *vm, size_t slot)
+{
+ uc_upvalref *curr = vm->open_upvals;
+ uc_upvalref *prev = NULL;
+ uc_upvalref *created;
+
+ while (curr && curr->slot > slot) {
+ prev = curr;
+ curr = curr->next;
+ }
+
+ if (curr && curr->slot == slot) {
+ if (vm->trace)
+ fprintf(stderr, " {+%zu} <%p> %s\n",
+ slot,
+ curr,
+ json_object_to_json_string(vm->stack.entries[slot]));
+
+ return curr;
+ }
+
+ created = uc_upvalref_new(slot);
+ created->next = curr;
+
+ if (vm->trace)
+ fprintf(stderr, " {*%zu} <%p> %s\n",
+ slot,
+ created,
+ json_object_to_json_string(vm->stack.entries[slot]));
+
+ if (prev)
+ prev->next = created;
+ else
+ vm->open_upvals = created;
+
+ return created;
+}
+
+static void
+uc_vm_close_upvals(uc_vm *vm, size_t slot)
+{
+ uc_upvalref *ref;
+
+ while (vm->open_upvals && vm->open_upvals->slot >= slot) {
+ ref = vm->open_upvals;
+ ref->value = uc_value_get(vm->stack.entries[ref->slot]);
+ ref->closed = true;
+
+ if (vm->trace)
+ fprintf(stderr, " {!%zu} <%p> %s\n",
+ ref->slot,
+ ref,
+ json_object_to_json_string(ref->value));
+
+ vm->open_upvals = ref->next;
+ json_object_put(ref->header.jso);
+ }
+}
+
+static void
+uc_vm_insn_load_closure(uc_vm *vm, enum insn_type insn)
+{
+ uc_callframe *frame = uc_vm_current_frame(vm);
+ json_object *fno = uc_chunk_get_constant(uc_vm_current_chunk(vm), vm->arg.u32);
+ uc_function *function = uc_object_as_function(fno);
+ uc_closure *closure = uc_closure_new(function, insn == I_ARFN);
+ volatile int32_t uv;
+ size_t i;
+
+ uc_vm_stack_push(vm, closure->header.jso);
+
+ for (i = 0; i < function->nupvals; i++) {
+ uv = (
+ frame->ip[0] * 0x1000000 +
+ frame->ip[1] * 0x10000 +
+ frame->ip[2] * 0x100 +
+ frame->ip[3]
+ ) - 0x7fffffff;
+
+ if (uv < 0)
+ closure->upvals[i] = uc_vm_capture_upval(vm, frame->stackframe - (uv + 1));
+ else
+ closure->upvals[i] = frame->closure->upvals[uv];
+
+ uc_value_get(closure->upvals[i]->header.jso);
+
+ frame->ip += 4;
+ }
+}
+
+static void
+uc_vm_insn_store_var(uc_vm *vm, enum insn_type insn)
+{
+ json_object *name, *v = uc_vm_stack_pop(vm);
+ uc_prototype *scope, *next;
+
+ scope = vm->globals;
+ name = uc_chunk_get_constant(uc_vm_current_chunk(vm), vm->arg.u32);
+
+ while (json_object_get_type(name) == json_type_string) {
+ if (json_object_object_get_ex(scope->header.jso, json_object_get_string(name), NULL))
+ break;
+
+ next = scope->parent;
+
+ if (!next) {
+ if (vm->config->strict_declarations) {
+ uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
+ "access to undeclared variable %s",
+ json_object_get_string(name));
+ }
+
+ break;
+ }
+
+ scope = next;
+ }
+
+ if (scope && json_object_get_type(name) == json_type_string)
+ json_object_object_add(scope->header.jso, json_object_get_string(name), uc_value_get(v));
+
+ uc_value_put(name);
+ uc_vm_stack_push(vm, v);
+}
+
+static void
+uc_vm_insn_store_val(uc_vm *vm, enum insn_type insn)
+{
+ json_object *v = uc_vm_stack_pop(vm);
+ json_object *k = uc_vm_stack_pop(vm);
+ json_object *o = uc_vm_stack_pop(vm);
+
+ uc_vm_stack_push(vm, uc_setval(o, k, v));
+
+ uc_value_put(o);
+ uc_value_put(k);
+}
+
+static void
+uc_vm_insn_store_upval(uc_vm *vm, enum insn_type insn)
+{
+ uc_callframe *frame = uc_vm_current_frame(vm);
+ uc_upvalref *ref = frame->closure->upvals[vm->arg.u32];
+ json_object *val = uc_value_get(uc_vm_stack_peek(vm, 0));
+
+ if (ref->closed) {
+ uc_value_put(ref->value);
+ ref->value = val;
+ }
+ else {
+ uc_vm_stack_set(vm, ref->slot, val);
+ }
+}
+
+static void
+uc_vm_insn_store_local(uc_vm *vm, enum insn_type insn)
+{
+ uc_callframe *frame = uc_vm_current_frame(vm);
+ json_object *val = uc_value_get(uc_vm_stack_peek(vm, 0));
+
+ uc_vm_stack_set(vm, frame->stackframe + vm->arg.u32, val);
+}
+
+static json_object *
+uc_vm_value_bitop(uc_vm *vm, enum insn_type operation, json_object *value, json_object *operand)
+{
+ json_object *rv = NULL;
+ int64_t n1, n2;
+ double d;
+
+ if (uc_cast_number(value, &n1, &d) == json_type_double)
+ n1 = isnan(d) ? 0 : (int64_t)d;
+
+ if (uc_cast_number(operand, &n2, &d) == json_type_double)
+ n2 = isnan(d) ? 0 : (int64_t)d;
+
+ switch (operation) {
+ case I_LSHIFT:
+ rv = xjs_new_int64(n1 << n2);
+ break;
+
+ case I_RSHIFT:
+ rv = xjs_new_int64(n1 >> n2);
+ break;
+
+ case I_BAND:
+ rv = xjs_new_int64(n1 & n2);
+ break;
+
+ case I_BXOR:
+ rv = xjs_new_int64(n1 ^ n2);
+ break;
+
+ case I_BOR:
+ rv = xjs_new_int64(n1 | n2);
+ break;
+
+ default:
+ break;
+ }
+
+ return rv;
+}
+
+static json_object *
+uc_vm_value_arith(uc_vm *vm, enum insn_type operation, json_object *value, json_object *operand)
+{
+ json_object *rv = NULL;
+ enum json_type t1, t2;
+ const char *s1, *s2;
+ size_t len1, len2;
+ int64_t n1, n2;
+ double d1, d2;
+ char *s;
+
+ if (operation > I_MOD)
+ return uc_vm_value_bitop(vm, operation, value, operand);
+
+ if (operation == I_ADD &&
+ (json_object_is_type(value, json_type_string) ||
+ json_object_is_type(operand, json_type_string))) {
+ s1 = value ? json_object_get_string(value) : "null";
+ s2 = operand ? json_object_get_string(operand) : "null";
+ len1 = strlen(s1);
+ len2 = strlen(s2);
+ s = xalloc(len1 + len2 + 1);
+
+ snprintf(s, len1 + len2 + 1, "%s%s", s1, s2);
+
+ rv = xjs_new_string(s);
+
+ free(s);
+
+ return rv;
+ }
+
+ t1 = uc_cast_number(value, &n1, &d1);
+ t2 = uc_cast_number(operand, &n2, &d2);
+
+ if (t1 == json_type_double || t2 == json_type_double) {
+ d1 = (t1 == json_type_double) ? d1 : (double)n1;
+ d2 = (t2 == json_type_double) ? d2 : (double)n2;
+
+ switch (operation) {
+ case I_ADD:
+ case I_PLUS:
+ rv = uc_double_new(d1 + d2);
+ break;
+
+ case I_SUB:
+ rv = uc_double_new(d1 - d2);
+ break;
+
+ case I_MUL:
+ rv = uc_double_new(d1 * d2);
+ break;
+
+ case I_DIV:
+ if (d2 == 0.0)
+ rv = uc_double_new(INFINITY);
+ else if (isnan(d2))
+ rv = uc_double_new(NAN);
+ else if (!isfinite(d2))
+ rv = uc_double_new(isfinite(d1) ? 0.0 : NAN);
+ else
+ rv = uc_double_new(d1 / d2);
+
+ break;
+
+ case I_MOD:
+ rv = uc_double_new(NAN);
+ break;
+
+ default:
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
+ "undefined arithmetic operation %d",
+ operation);
+ break;
+ }
+ }
+ else {
+ switch (operation) {
+ case I_ADD:
+ case I_PLUS:
+ rv = xjs_new_int64(n1 + n2);
+ break;
+
+ case I_SUB:
+ rv = xjs_new_int64(n1 - n2);
+ break;
+
+ case I_MUL:
+ rv = xjs_new_int64(n1 * n2);
+ break;
+
+ case I_DIV:
+ if (n2 == 0)
+ rv = uc_double_new(INFINITY);
+ else
+ rv = xjs_new_int64(n1 / n2);
+
+ break;
+
+ case I_MOD:
+ rv = xjs_new_int64(n1 % n2);
+ break;
+
+ default:
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
+ "undefined arithmetic operation %d",
+ operation);
+ break;
+ }
+ }
+
+ return rv;
+}
+
+static void
+uc_vm_insn_update_var(uc_vm *vm, enum insn_type insn)
+{
+ json_object *name, *val, *inc = uc_vm_stack_pop(vm);
+ uc_prototype *scope, *next;
+
+ scope = vm->globals;
+ name = uc_chunk_get_constant(uc_vm_current_chunk(vm), vm->arg.u32 & 0x00FFFFFF);
+
+ assert(json_object_is_type(name, json_type_string));
+
+ while (true) {
+ if (json_object_object_get_ex(scope->header.jso, json_object_get_string(name), &val))
+ break;
+
+ next = scope->parent;
+
+ if (!next) {
+ if (vm->config->strict_declarations) {
+ uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
+ "access to undeclared variable %s",
+ json_object_get_string(name));
+ }
+
+ break;
+ }
+
+ scope = next;
+ }
+
+ val = uc_vm_value_arith(vm, vm->arg.u32 >> 24, val, inc);
+
+ json_object_object_add(scope->header.jso, json_object_get_string(name), uc_value_get(val));
+ uc_vm_stack_push(vm, val);
+
+ uc_value_put(name);
+ uc_value_put(inc);
+}
+
+static void
+uc_vm_insn_update_val(uc_vm *vm, enum insn_type insn)
+{
+ json_object *inc = uc_vm_stack_pop(vm);
+ json_object *k = uc_vm_stack_pop(vm);
+ json_object *v = uc_vm_stack_pop(vm);
+ json_object *val = NULL;
+
+ switch (json_object_get_type(v)) {
+ case json_type_object:
+ case json_type_array:
+ val = uc_getval(v, k);
+ uc_vm_stack_push(vm, uc_setval(v, k, uc_vm_value_arith(vm, vm->arg.u8, val, inc)));
+ break;
+
+ default:
+ uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
+ "left-hand side expression is %s",
+ v ? "not an array or object" : "null");
+
+ break;
+ }
+
+ uc_value_put(val);
+ uc_value_put(inc);
+ uc_value_put(v);
+ uc_value_put(k);
+}
+
+static void
+uc_vm_insn_update_upval(uc_vm *vm, enum insn_type insn)
+{
+ uc_callframe *frame = uc_vm_current_frame(vm);
+ size_t slot = vm->arg.u32 & 0x00FFFFFF;
+ uc_upvalref *ref = frame->closure->upvals[slot];
+ json_object *inc = uc_vm_stack_pop(vm);
+ json_object *val;
+
+ if (ref->closed)
+ val = ref->value;
+ else
+ val = vm->stack.entries[ref->slot];
+
+ val = uc_vm_value_arith(vm, vm->arg.u32 >> 24, val, inc);
+
+ uc_vm_stack_push(vm, val);
+
+ uc_value_put(inc);
+
+ if (ref->closed) {
+ uc_value_put(ref->value);
+ ref->value = uc_value_get(uc_vm_stack_peek(vm, 0));
+ }
+ else {
+ uc_vm_stack_set(vm, ref->slot, uc_value_get(uc_vm_stack_peek(vm, 0)));
+ }
+}
+
+static void
+uc_vm_insn_update_local(uc_vm *vm, enum insn_type insn)
+{
+ uc_callframe *frame = uc_vm_current_frame(vm);
+ size_t slot = vm->arg.u32 & 0x00FFFFFF;
+ json_object *inc = uc_vm_stack_pop(vm);
+ json_object *val;
+
+ val = uc_vm_value_arith(vm, vm->arg.u32 >> 24,
+ vm->stack.entries[frame->stackframe + slot], inc);
+
+ uc_vm_stack_push(vm, val);
+
+ uc_value_put(inc);
+ uc_vm_stack_set(vm, frame->stackframe + slot, uc_value_get(uc_vm_stack_peek(vm, 0)));
+}
+
+static void
+uc_vm_insn_narr(uc_vm *vm, enum insn_type insn)
+{
+ json_object *arr = xjs_new_array_size(vm->arg.u32);
+
+ uc_vm_stack_push(vm, arr);
+}
+
+static void
+uc_vm_insn_parr(uc_vm *vm, enum insn_type insn)
+{
+ json_object *arr = uc_vm_stack_peek(vm, vm->arg.u32);
+ size_t idx;
+
+ for (idx = 0; idx < vm->arg.u32; idx++)
+ json_object_array_add(arr, uc_vm_stack_peek(vm, vm->arg.u32 - idx - 1));
+
+ for (idx = 0; idx < vm->arg.u32; idx++)
+ uc_vm_stack_pop(vm);
+
+ //uc_vm_shrink(state, vm->arg.u32);
+}
+
+static void
+uc_vm_insn_marr(uc_vm *vm, enum insn_type insn)
+{
+ json_object *src = uc_vm_stack_pop(vm);
+ json_object *dst = uc_vm_stack_peek(vm, 0);
+ size_t i;
+
+ if (!json_object_is_type(src, json_type_array)) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE,
+ "(%s) is not iterable",
+ json_object_to_json_string(src));
+
+ return;
+ }
+
+ for (i = 0; i < json_object_array_length(src); i++)
+ json_object_array_add(dst, uc_value_get(json_object_array_get_idx(src, i)));
+
+ uc_value_put(src);
+}
+
+static void
+uc_vm_insn_nobj(uc_vm *vm, enum insn_type insn)
+{
+ json_object *arr = xjs_new_object();
+
+ uc_vm_stack_push(vm, arr);
+}
+
+static void
+uc_vm_insn_sobj(uc_vm *vm, enum insn_type insn)
+{
+ json_object *obj = uc_vm_stack_peek(vm, vm->arg.u32);
+ size_t idx;
+
+ for (idx = 0; idx < vm->arg.u32; idx += 2) {
+ json_object_object_add(obj,
+ json_object_get_string(uc_vm_stack_peek(vm, vm->arg.u32 - idx - 1)),
+ uc_value_get(uc_vm_stack_peek(vm, vm->arg.u32 - idx - 2)));
+ }
+
+ for (idx = 0; idx < vm->arg.u32; idx++)
+ uc_value_put(uc_vm_stack_pop(vm));
+}
+
+static void
+uc_vm_insn_mobj(uc_vm *vm, enum insn_type insn)
+{
+ json_object *src = uc_vm_stack_pop(vm);
+ json_object *dst = uc_vm_stack_peek(vm, 0);
+ char *istr;
+ size_t i;
+
+ switch (json_object_get_type(src)) {
+ case json_type_object:
+ ; /* a label can only be part of a statement and a declaration is not a statement */
+ json_object_object_foreach(src, k, v)
+ json_object_object_add(dst, k, uc_value_get(v));
+
+ uc_value_put(src);
+ break;
+
+ case json_type_array:
+ for (i = 0; i < json_object_array_length(src); i++) {
+ xasprintf(&istr, "%zu", i);
+ json_object_object_add(dst, istr, uc_value_get(json_object_array_get_idx(src, i)));
+ free(istr);
+ }
+
+ uc_value_put(src);
+ break;
+
+ default:
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE,
+ "Value (%s) is not iterable",
+ json_object_to_json_string(src));
+
+ break;
+ }
+}
+
+static void
+uc_vm_insn_arith(uc_vm *vm, enum insn_type insn)
+{
+ json_object *r2 = uc_vm_stack_pop(vm);
+ json_object *r1 = uc_vm_stack_pop(vm);
+ json_object *rv;
+
+ rv = uc_vm_value_arith(vm, insn, r1, r2);
+
+ uc_value_put(r1);
+ uc_value_put(r2);
+
+ uc_vm_stack_push(vm, rv);
+}
+
+static void
+uc_vm_insn_plus_minus(uc_vm *vm, enum insn_type insn)
+{
+ struct json_object *v = uc_vm_stack_pop(vm);
+ bool is_sub = (insn == I_MINUS);
+ enum json_type t;
+ int64_t n;
+ double d;
+
+ t = uc_cast_number(v, &n, &d);
+
+ json_object_put(v);
+
+ switch (t) {
+ case json_type_int:
+ uc_vm_stack_push(vm, xjs_new_int64(is_sub ? -n : n));
+ break;
+
+ default:
+ uc_vm_stack_push(vm, uc_double_new(is_sub ? -d : d));
+ break;
+ }
+}
+
+static void
+uc_vm_insn_bitop(uc_vm *vm, enum insn_type insn)
+{
+ json_object *r2 = uc_vm_stack_pop(vm);
+ json_object *r1 = uc_vm_stack_pop(vm);
+ json_object *rv;
+
+ rv = uc_vm_value_bitop(vm, insn, r1, r2);
+
+ uc_value_put(r1);
+ uc_value_put(r2);
+
+ uc_vm_stack_push(vm, rv);
+}
+
+static void
+uc_vm_insn_complement(uc_vm *vm, enum insn_type insn)
+{
+ struct json_object *v = uc_vm_stack_pop(vm);
+ int64_t n;
+ double d;
+
+ if (uc_cast_number(v, &n, &d) == json_type_double)
+ n = isnan(d) ? 0 : (int64_t)d;
+
+ json_object_put(v);
+
+ uc_vm_stack_push(vm, xjs_new_int64(~n));
+}
+
+static void
+uc_vm_insn_rel(uc_vm *vm, enum insn_type insn)
+{
+ json_object *r2 = uc_vm_stack_pop(vm);
+ json_object *r1 = uc_vm_stack_pop(vm);
+ bool res = false;
+
+ switch (insn) {
+ case I_LT:
+ res = uc_cmp(TK_LT, r1, r2);
+ break;
+
+ case I_GT:
+ res = uc_cmp(TK_GT, r1, r2);
+ break;
+
+ case I_EQ:
+ res = uc_cmp(TK_EQ, r1, r2);
+ break;
+
+ case I_NE:
+ res = uc_cmp(TK_NE, r1, r2);
+ break;
+
+ default:
+ break;
+ }
+
+ uc_value_put(r1);
+ uc_value_put(r2);
+
+ uc_vm_stack_push(vm, xjs_new_boolean(res));
+}
+
+static void
+uc_vm_insn_in(uc_vm *vm, enum insn_type insn)
+{
+ json_object *r2 = uc_vm_stack_pop(vm);
+ json_object *r1 = uc_vm_stack_pop(vm);
+ json_object *item;
+ size_t arrlen, arridx;
+ bool found = false;
+ const char *key;
+
+ switch (json_object_get_type(r2)) {
+ case json_type_array:
+ for (arridx = 0, arrlen = json_object_array_length(r2);
+ arridx < arrlen; arridx++) {
+ item = json_object_array_get_idx(r2, arridx);
+
+ if (uc_cmp(TK_EQ, r1, item)) {
+ found = true;
+ break;
+ }
+ }
+
+ break;
+
+ case json_type_object:
+ key = r1 ? json_object_get_string(r1) : "null";
+ found = json_object_object_get_ex(r2, key, NULL);
+ break;
+
+ default:
+ found = false;
+ }
+
+ uc_value_put(r1);
+ uc_value_put(r2);
+
+ uc_vm_stack_push(vm, xjs_new_boolean(found));
+}
+
+static void
+uc_vm_insn_equality(uc_vm *vm, enum insn_type insn)
+{
+ json_object *r2 = uc_vm_stack_pop(vm);
+ json_object *r1 = uc_vm_stack_pop(vm);
+ bool equal = uc_eq(r1, r2);
+
+ uc_value_put(r1);
+ uc_value_put(r2);
+
+ uc_vm_stack_push(vm, xjs_new_boolean((insn == I_EQS) ? equal : !equal));
+}
+
+static void
+uc_vm_insn_not(uc_vm *vm, enum insn_type insn)
+{
+ json_object *r1 = uc_vm_stack_pop(vm);
+
+ uc_vm_stack_push(vm, xjs_new_boolean(!uc_val_is_truish(r1)));
+ uc_value_put(r1);
+}
+
+static void
+uc_vm_insn_jmp(uc_vm *vm, enum insn_type insn)
+{
+ uc_callframe *frame = uc_vm_current_frame(vm);
+ uc_chunk *chunk = uc_vm_frame_chunk(frame);
+ int32_t addr = vm->arg.s32;
+
+ /* ip already has been incremented */
+ addr -= 5;
+
+ if (frame->ip + addr < chunk->entries ||
+ frame->ip + addr >= chunk->entries + chunk->count) {
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "jump target out of range");
+ return;
+ }
+
+ frame->ip += addr;
+}
+
+static void
+uc_vm_insn_jmpz(uc_vm *vm, enum insn_type insn)
+{
+ uc_callframe *frame = uc_vm_current_frame(vm);
+ uc_chunk *chunk = uc_vm_frame_chunk(frame);
+ json_object *v = uc_vm_stack_pop(vm);
+ int32_t addr = vm->arg.s32;
+
+ /* ip already has been incremented */
+ addr -= 5;
+
+ if (frame->ip + addr < chunk->entries ||
+ frame->ip + addr >= chunk->entries + chunk->count) {
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "jump target out of range");
+ return;
+ }
+
+ if (!uc_val_is_truish(v))
+ frame->ip += addr;
+
+ uc_value_put(v);
+}
+
+static void
+uc_vm_insn_next(uc_vm *vm, enum insn_type insn)
+{
+ json_object *k = uc_vm_stack_pop(vm);
+ json_object *v = uc_vm_stack_pop(vm);
+ struct lh_entry *curr;
+ int64_t n;
+
+ switch (json_object_get_type(v)) {
+ case json_type_object:
+ curr = k ? json_object_get_userdata(k) : json_object_get_object(v)->head;
+
+ if (curr) {
+ if (!k)
+ k = xjs_new_string("[key]");
+
+ json_object_set_userdata(k, curr->next, NULL);
+
+ uc_vm_stack_push(vm, xjs_new_string(curr->k));
+
+ if (insn == I_NEXTKV)
+ uc_vm_stack_push(vm, uc_value_get((json_object *)curr->v));
+
+ uc_vm_stack_push(vm, k);
+
+ return;
+ }
+
+ break;
+
+ case json_type_array:
+ if (!k)
+ k = xjs_new_int64(0);
+
+ n = json_object_get_int64(k);
+
+ if (json_object_is_type(k, json_type_int) && n < json_object_array_length(v)) {
+ json_object_int_inc(k, 1);
+
+ if (insn == I_NEXTKV)
+ uc_vm_stack_push(vm, xjs_new_int64(n));
+
+ uc_vm_stack_push(vm, uc_value_get(json_object_array_get_idx(v, n)));
+
+ uc_vm_stack_push(vm, k);
+
+ return;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ uc_vm_stack_push(vm, NULL);
+ uc_vm_stack_push(vm, NULL);
+
+ if (insn == I_NEXTKV)
+ uc_vm_stack_push(vm, NULL);
+}
+
+static void
+uc_vm_insn_close_upval(uc_vm *vm, enum insn_type insn)
+{
+ uc_vm_close_upvals(vm, vm->stack.count - 1);
+ uc_value_put(uc_vm_stack_pop(vm));
+}
+
+static void
+uc_vm_insn_call(uc_vm *vm, enum insn_type insn)
+{
+ json_object *fno = uc_value_get(uc_vm_stack_peek(vm, vm->arg.u32 & 0xffff));
+ json_object *ctx = NULL;
+
+ if (!uc_object_is_type(fno, UC_OBJ_CLOSURE) || !uc_object_as_closure(fno)->is_arrow)
+ ctx = NULL;
+ else if (vm->callframes.count > 0)
+ ctx = uc_value_get(uc_vm_current_frame(vm)->ctx);
+
+ uc_vm_call_function(vm, ctx, fno, false, vm->arg.u32);
+}
+
+static void
+uc_vm_insn_mcall(uc_vm *vm, enum insn_type insn)
+{
+ size_t key_slot = vm->stack.count - (vm->arg.u32 & 0xffff) - 1;
+ json_object *ctx = vm->stack.entries[key_slot - 1];
+ json_object *key = vm->stack.entries[key_slot];
+ json_object *fno = uc_getval(ctx, key);
+
+ uc_vm_stack_set(vm, key_slot, fno);
+
+ /* arrow functions as method calls inherit the parent ctx */
+ if (uc_object_is_type(fno, UC_OBJ_CLOSURE) && uc_object_as_closure(fno)->is_arrow)
+ ctx = uc_vm_current_frame(vm)->ctx;
+
+ uc_vm_call_function(vm, uc_value_get(ctx), uc_value_get(fno), true, vm->arg.u32);
+}
+
+static void
+uc_vm_insn_print(uc_vm *vm, enum insn_type insn)
+{
+ json_object *v = uc_vm_stack_pop(vm);
+ const char *p;
+ size_t len;
+
+ switch (json_object_get_type(v)) {
+ case json_type_object:
+ case json_type_array:
+ p = json_object_to_json_string_ext(v, JSON_C_TO_STRING_NOSLASHESCAPE|JSON_C_TO_STRING_SPACED);
+ len = strlen(p);
+ break;
+
+ case json_type_string:
+ p = json_object_get_string(v);
+ len = json_object_get_string_len(v);
+ break;
+
+ case json_type_null:
+ p = "";
+ len = 0;
+ break;
+
+ default:
+ p = json_object_get_string(v);
+ len = strlen(p);
+ }
+
+ fwrite(p, 1, len, stdout);
+
+ uc_value_put(v);
+}
+
+static json_object *
+uc_vm_callframe_pop(uc_vm *vm)
+{
+ uc_callframe *frame = uc_vm_current_frame(vm);
+ json_object *retval;
+
+ /* close upvalues */
+ uc_vm_close_upvals(vm, frame->stackframe);
+
+ if (vm->stack.count > frame->stackframe)
+ retval = uc_vm_stack_pop(vm);
+ else
+ retval = NULL;
+
+ /* reset function stack frame */
+ while (vm->stack.count > frame->stackframe)
+ uc_value_put(uc_vm_stack_pop(vm));
+
+ /* for method calls, release context as well */
+ if (frame->mcall)
+ uc_value_put(uc_vm_stack_pop(vm));
+
+ /* release function */
+ uc_value_put(frame->closure ? frame->closure->header.jso : NULL);
+ uc_value_put(frame->cfunction ? frame->cfunction->header.jso : NULL);
+
+ /* release context */
+ uc_value_put(frame->ctx);
+
+ vm->callframes.count--;
+
+ return retval;
+}
+
+static void
+uc_vm_output_exception(uc_vm *vm)
+{
+ if (vm->exception.type == EXCEPTION_USER)
+ fprintf(stderr, "%s\n", vm->exception.message);
+ else
+ fprintf(stderr, "%s: %s\n",
+ exception_type_strings[vm->exception.type] ? exception_type_strings[vm->exception.type] : "Error",
+ vm->exception.message);
+
+ fprintf(stderr, "%s\n\n",
+ json_object_get_string(
+ json_object_object_get(
+ json_object_array_get_idx(vm->exception.stacktrace, 0), "context")));
+}
+
+static uc_vm_status_t
+uc_vm_execute_chunk(uc_vm *vm)
+{
+ uc_callframe *frame = uc_vm_current_frame(vm);
+ uc_chunk *chunk = uc_vm_frame_chunk(frame);
+ json_object *retval;
+ enum insn_type insn;
+
+ while (chunk) {
+ if (vm->trace)
+ uc_dump_insn(vm, frame->ip, (insn = uc_vm_decode_insn(vm, frame, chunk)));
+ else
+ insn = uc_vm_decode_insn(vm, frame, chunk);
+
+ switch (insn) {
+ case I_LOAD:
+ case I_LOAD8:
+ case I_LOAD16:
+ case I_LOAD32:
+ uc_vm_insn_load(vm, insn);
+ break;
+
+ case I_LREXP:
+ uc_vm_insn_load_regexp(vm, insn);
+ break;
+
+ case I_LNULL:
+ uc_vm_insn_load_null(vm, insn);
+ break;
+
+ case I_LTRUE:
+ case I_LFALSE:
+ uc_vm_insn_load_bool(vm, insn);
+ break;
+
+ case I_LTHIS:
+ uc_vm_stack_push(vm, uc_value_get(frame->ctx));
+ break;
+
+ case I_LVAR:
+ uc_vm_insn_load_var(vm, insn);
+ break;
+
+ case I_LVAL:
+ uc_vm_insn_load_val(vm, insn);
+ break;
+
+ case I_LUPV:
+ uc_vm_insn_load_upval(vm, insn);
+ break;
+
+ case I_LLOC:
+ uc_vm_insn_load_local(vm, insn);
+ break;
+
+ case I_CLFN:
+ case I_ARFN:
+ uc_vm_insn_load_closure(vm, insn);
+ break;
+
+ case I_NARR:
+ uc_vm_insn_narr(vm, insn);
+ break;
+
+ case I_PARR:
+ uc_vm_insn_parr(vm, insn);
+ break;
+
+ case I_MARR:
+ uc_vm_insn_marr(vm, insn);
+ break;
+
+ case I_NOBJ:
+ uc_vm_insn_nobj(vm, insn);
+ break;
+
+ case I_SOBJ:
+ uc_vm_insn_sobj(vm, insn);
+ break;
+
+ case I_MOBJ:
+ uc_vm_insn_mobj(vm, insn);
+ break;
+
+ case I_SVAR:
+ uc_vm_insn_store_var(vm, insn);
+ break;
+
+ case I_SVAL:
+ uc_vm_insn_store_val(vm, insn);
+ break;
+
+ case I_SUPV:
+ uc_vm_insn_store_upval(vm, insn);
+ break;
+
+ case I_SLOC:
+ uc_vm_insn_store_local(vm, insn);
+ break;
+
+ case I_UVAR:
+ uc_vm_insn_update_var(vm, insn);
+ break;
+
+ case I_UVAL:
+ uc_vm_insn_update_val(vm, insn);
+ break;
+
+ case I_UUPV:
+ uc_vm_insn_update_upval(vm, insn);
+ break;
+
+ case I_ULOC:
+ uc_vm_insn_update_local(vm, insn);
+ break;
+
+ case I_ADD:
+ case I_SUB:
+ case I_MUL:
+ case I_DIV:
+ case I_MOD:
+ uc_vm_insn_arith(vm, insn);
+ break;
+
+ case I_PLUS:
+ case I_MINUS:
+ uc_vm_insn_plus_minus(vm, insn);
+ break;
+
+ case I_LSHIFT:
+ case I_RSHIFT:
+ case I_BAND:
+ case I_BXOR:
+ case I_BOR:
+ uc_vm_insn_bitop(vm, insn);
+ break;
+
+ case I_COMPL:
+ uc_vm_insn_complement(vm, insn);
+ break;
+
+ case I_EQS:
+ case I_NES:
+ uc_vm_insn_equality(vm, insn);
+ break;
+
+ case I_EQ:
+ case I_NE:
+ case I_LT:
+ case I_GT:
+ uc_vm_insn_rel(vm, insn);
+ break;
+
+ case I_IN:
+ uc_vm_insn_in(vm, insn);
+ break;
+
+ case I_NOT:
+ uc_vm_insn_not(vm, insn);
+ break;
+
+ case I_JMP:
+ uc_vm_insn_jmp(vm, insn);
+ break;
+
+ case I_JMPZ:
+ uc_vm_insn_jmpz(vm, insn);
+ break;
+
+ case I_NEXTK:
+ case I_NEXTKV:
+ uc_vm_insn_next(vm, insn);
+ break;
+
+ case I_COPY:
+ uc_vm_stack_push(vm, uc_value_get(uc_vm_stack_peek(vm, vm->arg.u8)));
+ break;
+
+ case I_POP:
+ uc_value_put(uc_vm_stack_pop(vm));
+ break;
+
+ case I_CUPV:
+ uc_vm_insn_close_upval(vm, insn);
+ break;
+
+ case I_CALL:
+ uc_vm_insn_call(vm, insn);
+ frame = uc_vm_current_frame(vm);
+ chunk = frame->closure ? uc_vm_frame_chunk(frame) : NULL;
+ break;
+
+ case I_MCALL:
+ uc_vm_insn_mcall(vm, insn);
+ frame = uc_vm_current_frame(vm);
+ chunk = frame->closure ? uc_vm_frame_chunk(frame) : NULL;
+ break;
+
+ case I_RETURN:
+ retval = uc_vm_callframe_pop(vm);
+
+ if (vm->callframes.count == 0) {
+ uc_value_put(retval);
+
+ return STATUS_OK;
+ }
+
+ uc_vm_stack_push(vm, retval);
+
+ frame = uc_vector_last(&vm->callframes);
+ chunk = uc_vm_frame_chunk(frame);
+ break;
+
+ case I_PRINT:
+ uc_vm_insn_print(vm, insn);
+ break;
+
+ default:
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "unknown opcode %d", insn);
+ break;
+ }
+
+ /* previous instruction raised exception */
+ if (vm->exception.type != EXCEPTION_NONE) {
+ /* walk up callframes until something handles the exception or the root is reached */
+ while (!uc_vm_handle_exception(vm)) {
+ /* no further callframe to pop, report unhandled exception and terminate */
+ if (vm->callframes.count == 1) {
+ uc_vm_output_exception(vm);
+
+ return ERROR_RUNTIME;
+ }
+
+ /* if VM returned into native function, don't bubble up */
+ if (!chunk)
+ return ERROR_RUNTIME;
+
+ /* no exception handler in current function, pop callframe */
+ uc_value_put(uc_vm_callframe_pop(vm));
+
+ /* resume execution at topmost remaining callframe */
+ frame = uc_vector_last(&vm->callframes);
+ chunk = uc_vm_frame_chunk(frame);
+ }
+ }
+ }
+
+ return STATUS_OK;
+}
+
+static uc_vm_status_t
+uc_vm_preload(uc_vm *vm, json_object *modules)
+{
+ json_object *requirefn, *module, *name;
+ uc_exception_type_t ex;
+ size_t i;
+
+ if (!json_object_is_type(modules, json_type_array))
+ return STATUS_OK;
+
+ requirefn = json_object_object_get(vm->globals->header.jso, "require");
+
+ if (!uc_object_is_type(requirefn, UC_OBJ_CFUNCTION))
+ return STATUS_OK;
+
+ for (i = 0; i < json_object_array_length(modules); i++) {
+ name = json_object_array_get_idx(modules, i);
+
+ uc_vm_stack_push(vm, uc_value_get(requirefn));
+ uc_vm_stack_push(vm, uc_value_get(name));
+
+ ex = uc_vm_call(vm, false, 1);
+
+ if (ex)
+ return ERROR_RUNTIME;
+
+ module = uc_vm_stack_pop(vm);
+
+ uc_value_put(uc_setval(vm->globals->header.jso, name, module));
+ }
+
+ return STATUS_OK;
+}
+
+uc_vm_status_t
+uc_vm_execute(uc_vm *vm, uc_function *fn, uc_prototype *globals, json_object *modules)
+{
+ uc_closure *closure = uc_closure_new(fn, false);
+ uc_callframe *frame;
+ uc_vm_status_t rv;
+
+ vm->globals = globals;
+
+ uc_vector_grow(&vm->callframes);
+
+ frame = &vm->callframes.entries[vm->callframes.count++];
+ frame->closure = closure;
+ frame->stackframe = 0;
+ frame->ip = uc_vm_frame_chunk(frame)->entries;
+
+ if (vm->trace) {
+ size_t msglen = 0;
+ char *msg = NULL;
+
+ format_source_context(&msg, &msglen,
+ fn->source, 0, true);
+
+ fprintf(stderr, "%s", msg);
+
+ uc_vm_frame_dump(vm, frame);
+ }
+
+ //uc_vm_stack_push(vm, closure->header.jso);
+ uc_vm_stack_push(vm, NULL);
+
+ rv = uc_vm_preload(vm, modules);
+
+ if (rv != STATUS_OK)
+ uc_vm_output_exception(vm);
+ else
+ rv = uc_vm_execute_chunk(vm);
+
+ uc_value_put(vm->globals->header.jso);
+ vm->globals = NULL;
+
+ return rv;
+}
+
+uc_exception_type_t
+uc_vm_call(uc_vm *vm, bool mcall, size_t nargs)
+{
+ json_object *ctx = mcall ? uc_value_get(uc_vm_stack_peek(vm, nargs - 1)) : NULL;
+ json_object *fno = uc_value_get(uc_vm_stack_peek(vm, nargs));
+
+ if (uc_vm_call_function(vm, ctx, fno, mcall, nargs & 0xffff)) {
+ if (!uc_object_is_type(fno, UC_OBJ_CFUNCTION))
+ uc_vm_execute_chunk(vm);
+ }
+
+ return vm->exception.type;
+}