/* vm.c */

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
#include <sys/time.h>
#include <poll.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <math.h>

#include "vm.h"
#include "code.h"
#include "dmx.h"
#include "hash.h"
#include "mem.h"
#include "midi.h"
#include "beatdetect.h"
#include "map3d.h"
#include "mouse.h"

#define DEBUG 0

typedef char instr;

struct vm_thread {
	instr *pc;
	stkentry *sp;
	stkentry *stackbase;
	size_t stacksize;
	int queue;
	struct timeval time;
	struct vm_thread *next;
	struct vm_thread *prev;
};

#define VM_MAXPOLLFD	16

#define ARRAYBLOCKSIZE	512

struct pollfd vm_pollfd[VM_MAXPOLLFD];
int vm_pollfdqueue[VM_MAXPOLLFD];

int vm_npollfds;

volatile int vm_caughtsignal;

struct vm_thread *vm_queues[VM_MAXQUEUES];


struct vm_thread *vm_current = NULL;

instr *vm_codearea = NULL;
size_t vm_codesize = 0;

struct hashentry *fnhash[HASHSIZE];
struct hashentry *globhash[HASHSIZE];
struct hashentry *arrayhash[HASHSIZE];

#define VM_CODESIZE 65536
#define VM_STACKSIZE 65536

#define VM_STRING_MAX 1024

#define GLOB_MAXNAMELEN	1024

void vm_destroy(struct vm_thread *);
void vm_unqueue(struct vm_thread *);
void vm_queue(struct vm_thread *, int);

#include "abi.h"

extern vm_intfn vm_intfn_table[];
extern const int vm_intfn_size;

void stack_push(struct vm_thread *thread, stkentry e);
void stack_pop(struct vm_thread *thread, int count);
stkentry stack_get(struct vm_thread *thread, int count);
float stack_getreal(struct vm_thread *thread, int count);
char *stack_getstr(struct vm_thread *thread, int len, int count);
void stack_poke(struct vm_thread *thread, int count, stkentry value);
void stack_pokereal(struct vm_thread *thread, int count, float value);

int vm_intfn_nop(void)
{
	/* Do nothing */
	return 1;
}

/*
 * global_store and global_load are special. 
 * The compiler does not allocate space for a return value.
 * They also change the stack pointer.
 */
int vm_intfn_global_store(void)
{
	int len, next, value;
	struct hashentry *hashptr;
	char varname[GLOB_MAXNAMELEN+1];
	char *vptr;

	len = stack_get(vm_current, 0);
	next = (len + sizeof(stkentry)-1) / sizeof(stkentry) + 1;

	if (len > GLOB_MAXNAMELEN)
		goto gstoreout;

	vptr = stack_getstr(vm_current, len, 0);
	value = stack_get(vm_current, next);

	strncpy(varname, vptr, len);
	varname[len] = '\0';

	hashptr = hash_lookup(globhash, varname, 1);
	assert(hashptr != NULL);

	if (hashptr->name == NULL) {
		hashptr->name = safe_malloc(strlen(varname)+1);
		strcpy(hashptr->name, varname);
	}

	hashptr->value = value;

gstoreout:
	stack_pop(vm_current, next+1);

	return 1;
}

int vm_intfn_global_load(void)
{
	int len, next, value;
	struct hashentry *hashptr;
	char varname[GLOB_MAXNAMELEN+1];
	char *vptr;

	len = stack_get(vm_current, 0);
	next = (len + sizeof(stkentry)-1) / sizeof(stkentry) + 1;

	if (len > GLOB_MAXNAMELEN) {
		value = 0;
		goto gloadout;
	}

	vptr = stack_getstr(vm_current, len, 0);

	strncpy(varname, vptr, len);
	varname[len] = '\0';

	hashptr = hash_lookup(globhash, varname, 0);
	if (hashptr)
		value = hashptr->value;
	else
		value = 0;

gloadout:
	stack_pop(vm_current, next);
	stack_push(vm_current, value);

	return 1;
}

int vm_intfn_global_array_store(void)
{
	int len, next, value, index;
	struct hashentry *hashptr;
	long *arrayptr;
	char varname[GLOB_MAXNAMELEN+1];
	char *vptr;

	len = stack_get(vm_current, 0);
	next = (len + sizeof(stkentry)-1) / sizeof(stkentry) + 1;

	if (len > GLOB_MAXNAMELEN)
		goto gstorearrayout;

	vptr = stack_getstr(vm_current, len, 0);
	index = stack_get(vm_current, next);
	value = stack_get(vm_current, next+1);

	strncpy(varname, vptr, len);
	varname[len] = '\0';

	hashptr = hash_lookup(arrayhash, varname, 1);
	assert(hashptr != NULL);

	if (hashptr->name == NULL) {
		hashptr->name = safe_malloc(strlen(varname)+1);
		strcpy(hashptr->name, varname);
		hashptr->value = (int)safe_malloc(ARRAYBLOCKSIZE*sizeof(long));
		hashptr->flags = ARRAYBLOCKSIZE;
	}

	if (index >= hashptr->flags) {
		hashptr->flags = index + ARRAYBLOCKSIZE;
		hashptr->flags -= hashptr->flags % ARRAYBLOCKSIZE;
		hashptr->value = (int)safe_realloc((void *)hashptr->value,
		    hashptr->flags * sizeof(long));
	}

	arrayptr = (long *)hashptr->value;
	arrayptr[index] = value;

gstorearrayout:
	stack_pop(vm_current, next+2);

	return 1;
}

int vm_intfn_global_array_load(void)
{
	int len, next, value, index;
	struct hashentry *hashptr;
	long *arrayptr;
	char varname[GLOB_MAXNAMELEN+1];
	char *vptr;

	len = stack_get(vm_current, 0);
	next = (len + sizeof(stkentry)-1) / sizeof(stkentry) + 1;

	if (len > GLOB_MAXNAMELEN) {
		value = 0;
		goto gloadarrayout;
	}

	vptr = stack_getstr(vm_current, len, 0);
	index = stack_get(vm_current, next);

	strncpy(varname, vptr, len);
	varname[len] = '\0';

	hashptr = hash_lookup(arrayhash, varname, 0);
	if (hashptr) {
		arrayptr = (long *)hashptr->value;
		if (index < hashptr->flags) {
			value = arrayptr[index];
		} else {
			value = 0;
		}
	} else
		value = 0;

gloadarrayout:
	stack_pop(vm_current, next+1);
	stack_push(vm_current, value);

	return 1;
}

int vm_intfn_printint(void)
{
	printf("%d", stack_get(vm_current, 1));
	return 1;
}

int vm_intfn_printreal(void)
{
	printf("%f", (float)stack_getreal(vm_current, 1));
	return 1;
}

int vm_intfn_printstr(void)
{
	char format[13];
	int len = stack_get(vm_current, 1);

	snprintf(format, 13, "%%.%ds", len);
	printf(format, stack_getstr(vm_current, len, 1));

	return 1;
}

int vm_intfn_dmxsetchannel(void)
{
	int channel;
	int value;

	channel = stack_get(vm_current, 2);
	value = stack_get(vm_current, 1);
//	printf("channel %d = %d\n", channel, value);
	dmx_setchannel(channel, value);
	return 1;
}

int vm_intfn_dmxoutput(void)
{
	dmx_output();
	return 1;
}

int vm_intfn_gettime(void)
{
	struct timeval tv;
	if (gettimeofday(&tv, NULL))
		err(1, "gettimeofday() failed");
	stack_poke(vm_current, 1, tv.tv_sec);	/* arg1 */
	stack_poke(vm_current, 0, tv.tv_usec);	/* return value */
	return 1;
}

int vm_intfn_waittime(void)
{
	struct vm_thread *ptr;
	struct vm_thread *last;
	vm_current->time.tv_sec = stack_get(vm_current, 2);
	vm_current->time.tv_usec = stack_get(vm_current, 1);

	ptr = vm_queues[VM_TIMEQ];
	last = NULL;
	/*
	 * We need to insert into time queue in time order. Because
	 * of this, we're going to manually insert into the queue here.
	 * Make sure that this is kept in sync with vm_queue().
	 */
	while (ptr && timercmp(&vm_current->time, &ptr->time, >=)) {
		last = ptr;
		ptr = ptr->next;
	}
	vm_current->prev = last;
	vm_current->next = ptr;
	vm_current->queue = VM_TIMEQ;
	if (last)
		last->next = vm_current;
	else
		vm_queues[VM_TIMEQ] = vm_current;
	if (ptr)
		ptr->prev = vm_current;

	vm_current = NULL;
	return 1;
}

int vm_intfn_wait(void)
{
	int queue = stack_get(vm_current, 1);

	if ((queue < VM_USERQMIN) || (queue >= VM_MAXQUEUES)) {
		printf("Attempt to wait on invalid wait queue\n");
		vm_destroy(vm_current);
		vm_current = NULL;
	}

	vm_unqueue(vm_current);
	vm_queue(vm_current, queue);
	vm_current = NULL;
	return 1;
}

void vm_wakeup(int queue)
{
	struct vm_thread *thread;

	while ((thread = vm_queues[queue]) != NULL) {
		vm_unqueue(thread);
		vm_queue(thread, VM_RUNQ);
	}
}

int vm_intfn_wakeup(void)
{
	int queue = stack_get(vm_current, 1);

	if ((queue < VM_USERQMIN) || (queue >= VM_MAXQUEUES)) {
		printf("Attempt to wake up invalid wait queue\n");
		return 1;
	}
	vm_wakeup(queue);
	return 1;
}

int vm_intfn_spawn(void)
{
	char buf[VM_STRING_MAX];
	int len = stack_get(vm_current, 1);
	if (len > VM_STRING_MAX) {
		printf("Excessive string length - can't spawn\n");
		return 1;
	}
	strncpy(buf, stack_getstr(vm_current, len, 1), len);
	buf[len] = '\0';
	vm_spawn(buf);
	return 1;
}

int vm_intfn_midi_read(void)
{
	while (midi_read()) {
		if (midi_filter()) {
			int button, value;
			midi_getcmd(&button, &value);
			stack_poke(vm_current, 1, button);   /* arg1 */
			stack_poke(vm_current, 0, value);  /* return value */
			return 1;
		}
	}
	vm_queue(vm_current, VM_MIDIQ);
	vm_current = NULL;
	return 0;
}

int vm_intfn_mouse_read(void)
{
	while (mouse_read()) {
		if (mouse_filter()) {
			int x, y, z;
			mouse_getpos(&x, &y, &z);
			stack_poke(vm_current, 2, x);   /* arg1 */
			stack_poke(vm_current, 1, y);   /* arg2 */
			stack_poke(vm_current, 0, z);  /* return value */
			return 1;
		}
	}
	vm_queue(vm_current, VM_MOUSEQ);
	vm_current = NULL;
	return 0;
}

int vm_intfn_beatdetect_read(void)
{
	if (!beatdetect_read()) {
		vm_queue(vm_current, VM_BEATQ);
		vm_current = NULL;
		return 0;
	}
	return 1;
}

int vm_intfn_beatdetect_phase(void)
{
	stack_pokereal(vm_current, 0, (float)beatdetect_getphase());
	return 1;
}

int vm_intfn_beatdetect_confidence(void)
{
	stack_pokereal(vm_current, 0, (float)beatdetect_getconfidence());
	return 1;
}

int vm_intfn_realtoint(void)
{
	stack_poke(vm_current, 0, (int)stack_getreal(vm_current, 1));
	return 1;
}

int vm_intfn_inttoreal(void)
{
	stack_pokereal(vm_current, 0, (float)stack_get(vm_current, 1));
	return 1;
}

int vm_intfn_map3d_setcal(void)
{
	map3d_setcal(stack_get(vm_current, 7), /* light */
	    stack_get(vm_current, 6), /* n */
	    (double)stack_getreal(vm_current, 5), /* x */
	    (double)stack_getreal(vm_current, 4), /* y */
	    (double)stack_getreal(vm_current, 3), /* v */
	    stack_get(vm_current, 2), /* pan */
	    stack_get(vm_current, 1)); /* tilt */
	return 1;
}

int vm_intfn_map3d_calibrate(void)
{
	stack_poke(vm_current, 0, map3d_calibrate(stack_get(vm_current, 1)));
	return 1;
}

int vm_intfn_map3d_transform(void)
{
	int pan, tilt;

	map3d_transform(stack_get(vm_current, 4), /* light */
	    (double)stack_getreal(vm_current, 3), /* x */
	    (double)stack_getreal(vm_current, 2), /* y */
	    (double)stack_getreal(vm_current, 1), /* z */
	    &pan, &tilt);
	stack_poke(vm_current, 4, pan);	/* arg1 */
	stack_poke(vm_current, 0, tilt); /* return value */
	return 1;
}

int vm_intfn_map3d_setparams(void)
{
	map3d_setparams(stack_get(vm_current, 6), /* light */
	    (double)stack_get(vm_current, 5), /* opan */
	    (double)stack_get(vm_current, 4), /* otilt */
	    (double)stack_getreal(vm_current, 3), /* lpan */
	    (double)stack_getreal(vm_current, 2), /* ltilt */
	    (double)stack_getreal(vm_current, 1)); /* dist */
	return 1;
}

int vm_intfn_map3d_load(void)
{
	stack_poke(vm_current, 0, map3d_load());
	return 1;
}

int vm_intfn_map3d_save(void)
{
	map3d_save();
	return 1;
}

int vm_intfn_sin(void)
{
	stack_pokereal(vm_current, 0, sinf(stack_getreal(vm_current, 1)));
	return 1;
}

int vm_intfn_cos(void)
{
	stack_pokereal(vm_current, 0, cosf(stack_getreal(vm_current, 1)));
	return 1;
}

int vm_intfn_random(void)
{
	stack_poke(vm_current, 0, random() % stack_get(vm_current, 1));
	return 1;
}

void vm_sighandler(int signal)
{
	vm_caughtsignal = 1;
}

void vm_wakeupafterpoll(void)
{
	int i;

	for (i = 0; i < vm_npollfds; i++)
		if (vm_pollfd[i].revents & POLLRDNORM) {
			vm_wakeup(vm_pollfdqueue[i]);
		}
}

void vm_handle_signal(void)
{
	int rv;

	vm_caughtsignal = 0;

	fflush(stdout);
	rv = poll(vm_pollfd, vm_npollfds, 0);
	if (rv == -1) {
		printf("poll() returned an error\n");
		exit(1);
	}
	if (rv == 0)
		return;

	vm_wakeupafterpoll();
}

void vm_init(void)
{
	vm_codesize = VM_CODESIZE;
	vm_codearea = safe_malloc(vm_codesize);
	bzero(vm_queues, sizeof(struct vm_thread *) * VM_MAXQUEUES);
	vm_current = NULL;
	hash_init(fnhash);
	hash_init(globhash);
	hash_init(arrayhash);
	vm_npollfds = 0;
	vm_caughtsignal = 0;
	signal(SIGIO, vm_sighandler);
}

void vm_load_file(char *filename)
{
	FILE *fh;
	char *ptr;

	fh = fopen(filename, "r");
	assert(fh != NULL);

	for (ptr = vm_codearea; ptr < vm_codearea+vm_codesize; ptr++) {
		int c;
		c = fgetc(fh);
		if (c == EOF)
			break;
		*ptr = c;
	}

	fclose(fh);
}

#define GETINT(p, o) (((p[o] & 0xff) << 24) | \
			((p[o+1] & 0xff) << 16) | \
			((p[o+2] & 0xff) << 8) | \
			(p[o+3] & 0xff))

void vm_init_functions(void)
{
	int t;
	int len, type, fn, nargs;
	char *name;
	struct hashentry *ptr;

	/* First, let's check the magic */
	assert(vm_codearea[0] == MAGIC1);
	assert(vm_codearea[1] == MAGIC2);
	assert(vm_codearea[2] == VERSION1);
	assert(vm_codearea[3] == VERSION2);


	/* Now, get the function table pointer */
	t = GETINT(vm_codearea, 4);

	while ((len = GETINT(vm_codearea, t)) != 0) {
		type = GETINT(vm_codearea, t+4);
		if (type == 0) {
			fn = GETINT(vm_codearea, t+8);
			nargs = GETINT(vm_codearea, t+12);
			name = vm_codearea + t + 16;
			ptr = hash_lookup(fnhash, name, 1);

			printf("Function %s\n", name);
			fflush(stdout);

			if (ptr->name)
				err(1, "Function already exists");
			ptr->name = name;
			ptr->value = fn;
			ptr->flags = nargs;
		}
		t += len;
	}
}

void vm_load(char *filename)
{
	vm_load_file(filename);
	vm_init_functions();
}

void vm_unqueue(struct vm_thread *thread)
{
	if (thread->prev == NULL) {
		vm_queues[thread->queue] = thread->next;
	} else {
		thread->prev->next = thread->next;
	}

	if (thread->next)
		thread->next->prev = thread->prev;

	thread->next = NULL;
	thread->prev = NULL;
	thread->queue = VM_NOQUEUE;
}


/* Thread must not be queued anywhere else before calling this */
void vm_queue(struct vm_thread *thread, int queue)
{
	assert(thread != NULL);
	assert(thread->queue == VM_NOQUEUE);
	thread->prev = NULL;
	thread->next = vm_queues[queue];
	thread->queue = queue;
	vm_queues[queue] = thread;
}

int vm_spawn(char *fn)
{
	struct vm_thread *newt;
	struct hashentry *ptr;

	ptr = hash_lookup(fnhash, fn, 0);
	if (ptr == NULL) {
		printf("%s: No such function\n", fn);
		return 0;
	}

	printf("Spawning %s\n", fn);

	newt = malloc(sizeof(struct vm_thread));
	if (newt == NULL) {
		printf("malloc failed while spawning\n");
		return 0;
	}

	newt->pc = vm_codearea + ptr->value;
	printf("Starting execution at %p, %p, %x\n", newt->pc, vm_codearea, ptr->value);
	newt->stacksize = VM_STACKSIZE;
	newt->stackbase = malloc(newt->stacksize * sizeof(stkentry));
	if (newt->stackbase == NULL) {
		printf("malloc failed while spawning\n");
		free(newt);
		return 0;
	}
	newt->sp = newt->stackbase;
	/* Push return address here, to point to some special thread exit
	   routine */

	stack_push(newt, 0); 	/* Return value */
	stack_push(newt, 0);	/* Return address */

	/* Insert into head of run queue */
	newt->prev = NULL;
	newt->queue = VM_NOQUEUE;
	vm_queue(newt, VM_RUNQ);

	return 1;
}

void vm_destroy(struct vm_thread *thread)
{
	vm_unqueue(thread);
	free(thread->stackbase);
	free(thread);
}

int vm_runnable(struct vm_thread *thread)
{
	struct timeval tv;
	if (gettimeofday(&tv, NULL))
		err(1, "gettimeofday() failed");
	//printf("Runnable? %ld, %ld <= %ld, %ld\n", thread->time.tv_sec, thread->time.tv_usec, tv.tv_sec, tv.tv_usec);
	return timercmp(&thread->time, &tv, <=);
}

void vm_sched(void)
{
	if (vm_current) {
		vm_queue(vm_current, VM_RUNQ); /* XXX insert at tail */
		vm_current = NULL;
	}

	/* Search for waiting thread to move to runq */
	if (vm_queues[VM_TIMEQ] && vm_runnable(vm_queues[VM_TIMEQ])) {
		struct vm_thread *thread;
		thread = vm_queues[VM_TIMEQ];
		vm_unqueue(thread);
		vm_queue(thread, VM_RUNQ);
	}

	/* Go for next runnable thread */

	if (vm_queues[VM_RUNQ]) {
		vm_current = vm_queues[VM_RUNQ];
		vm_unqueue(vm_current);
	} else {
		struct timeval tv;
		struct timespec ts;
		int rv;

		if (vm_queues[VM_TIMEQ] == NULL) {
			printf("No runnable thread, and no waiting thread\n");
			exit(0);
		}
//		printf("No runnable thread - sleeping\n");
		gettimeofday(&tv, NULL);
		timersub(&vm_queues[VM_TIMEQ]->time, &tv, &tv);
		if ((tv.tv_sec < 0) || (tv.tv_usec < 0)) {
			tv.tv_sec = 0;
			tv.tv_usec = 0;
		}
		TIMEVAL_TO_TIMESPEC(&tv, &ts);
//		nanosleep(&ts, NULL);
		rv = pollts(vm_pollfd, vm_npollfds, &ts, NULL);
		if ((rv == -1) && (errno != EINTR))
			err(1, "pollts() error");
		if (rv > 0)
			vm_wakeupafterpoll();
	}
}

void vm_register_signal_fd(int fd, int queue)
{
	int rv;

	if (vm_npollfds >= VM_MAXPOLLFD)
		err(1, "Too many fds registered");
	vm_pollfd[vm_npollfds].fd = fd;
	vm_pollfd[vm_npollfds].events = POLLRDNORM;
	vm_pollfdqueue[vm_npollfds] = queue;
	vm_npollfds++;
	rv = fcntl(fd, F_SETFL, O_NONBLOCK | O_ASYNC);
}

void stack_push(struct vm_thread *thread, stkentry e)
{
	thread->sp++;
	if (thread->sp >= (thread->stackbase + thread->stacksize)) {
		printf("Stack overflow\n");
		exit(1);
		/* XXX what to do here? */
		thread->sp--;
		return;
	}
	*thread->sp = e;
}

void stack_pushstr(struct vm_thread *thread, int len, char *string)
{
	stkentry *strbase = thread->sp + 1;

	thread->sp += 1+((len+sizeof(stkentry)-1)/sizeof(stkentry));

	if (thread->sp >= (thread->stackbase + thread->stacksize)) {
		printf("Stack overflow\n");
		/* XXX what to do here? */
		thread->sp = thread->stackbase + thread->stacksize - 1;
		return;
	}
	strncpy((char *)strbase, string, len);
	*thread->sp = len;
}

char *stack_getstr(struct vm_thread *thread, int len, int count)
{
	stkentry *ptr;

	ptr = thread->sp - count -
	    ((len+sizeof(stkentry)-1)/sizeof(stkentry));
	if (ptr < thread->stackbase) {
		printf("Stack underflow\n");
		/* XXX what to do here? */
		ptr = thread->stackbase;
	}
	return (char *)ptr;
}

void stack_pop(struct vm_thread *thread, int count)
{
	thread->sp -= count;
	if (thread->sp < thread->stackbase) {
		printf("Stack underflow\n");
		/* XXX what to do here? */
		thread->sp = thread->stackbase;
	}
}

void stack_alloc(struct vm_thread *thread, int count)
{
	thread->sp += count;
	if (thread->sp >= (thread->stackbase + thread->stacksize)) {
		printf("Stack overflow\n");
		/* XXX what to do here? */
		thread->sp = thread->stackbase + thread->stacksize - 1;
	}
}

void stack_load(struct vm_thread *thread, int count)
{
	stkentry *ptr;

	ptr = thread->sp - count;
	if (ptr < thread->stackbase) {
		printf("Stack underflow\n");
		/* XXX what to do here? */
		ptr = thread->stackbase;
	}
	stack_push(thread, *ptr);
}

void stack_store(struct vm_thread *thread, int count)
{
	stkentry *ptr;

	ptr = thread->sp - count - 1;
	if (ptr < thread->stackbase) {
		printf("Stack underflow\n");
		/* XXX what to do here? */
		ptr = thread->stackbase;
	}
	*ptr = *thread->sp;
	stack_pop(thread, 1);
}

stkentry stack_get(struct vm_thread *thread, int count)
{
	stkentry *ptr;

	ptr = thread->sp - count;
	if (ptr < thread->stackbase) {
		printf("Stack underflow\n");
		/* XXX what to do here? */
		ptr = thread->stackbase;
	}
	return *ptr;
}

float stack_getreal(struct vm_thread *thread, int count)
{
	union {
		stkentry e;
		float f;
	} conv;
	conv.e = stack_get(thread, count);
	return conv.f;	/* XXX Not exactly MI */
}

void stack_poke(struct vm_thread *thread, int count, stkentry value)
{
	stkentry *ptr;

	ptr = thread->sp - count;
	if (ptr < thread->stackbase) {
		printf("Stack underflow\n");
		/* XXX what to do here? */
		ptr = thread->stackbase;
	}
	*ptr = value;
}

void stack_pokereal(struct vm_thread *thread, int count, float value)
{
	union {
		stkentry e;
		float f;
	} conv;
	conv.f = value;
	stack_poke(thread, count, conv.e);
}

#define INTOP	intop = 0;						\
		while (bytecount--)					\
			intop = (intop << 8) | ((*vm_current->pc++) & 0xff);

/*
 * RESTART can only be used to restart things that don't change the stack.
 * It is intended for use to restart internal functions if they need to sleep.
 */
#define RESTART	savedthread->pc = savedpc;

#define NOTIMPL		printf("Not implemented\n");

#define STACKTOP	(*vm_current->sp)
#define STACKTOPFL	(*(float *)(vm_current->sp))

void vm_run(void)
{
	int opcode;
	int bytecount;
	int intop;
	int val;
	float flval;
	instr *savedpc;
	struct vm_thread *savedthread;

	while (1) {
		if (vm_caughtsignal) {
			if (vm_current) {
				vm_queue(vm_current, VM_RUNQ);
				vm_current = NULL;
			}
			vm_handle_signal();
		}
		while (vm_current == NULL)
			vm_sched();
		if ((vm_current->pc < vm_codearea) ||
		    (vm_current->pc > (vm_codearea + vm_codesize))) {
			printf("Execution outside code area\n");
			vm_destroy(vm_current);
			vm_current = NULL;
			continue;
		}
		savedpc = vm_current->pc;
		savedthread = vm_current;
		opcode = *vm_current->pc++;
		bytecount = BYTECOUNT(opcode);
		opcode = OPCODE_MASK(opcode);
#if DEBUG
		printf("executing opcode %s (%d) at %p (bytecount %d)\n", instr_names[opcode], opcode, vm_current->pc-1, bytecount);
		printf("sp = %p\n", vm_current->sp);
#endif
		switch(opcode) {
		case OP_NOP:	/* No-op */
			break;
		case OP_PUSH:	/* Push immediate */
			INTOP
			stack_push(vm_current, intop);
			break;
		case OP_PUSHSTR:
			INTOP
			stack_pushstr(vm_current, intop, vm_current->pc);
			vm_current->pc += intop;
			break;
		case OP_POP:
			INTOP
			stack_pop(vm_current, intop);
			break;
		case OP_ALLOC:
			INTOP
			stack_alloc(vm_current, intop);
			break;
		case OP_LOAD:
			INTOP
			stack_load(vm_current, intop);
			break;
		case OP_STORE:
			INTOP
			stack_store(vm_current, intop);
			break;

		case OP_B:
		case OP_BL:
			INTOP
			if (opcode == OP_BL)
				stack_push(vm_current,
				    (stkentry)vm_current->pc);
			vm_current->pc += intop;
			break;
		case OP_BI:
		case OP_BIL:
			val = STACKTOP;
			if (opcode == OP_BIL)
				stack_push(vm_current,
				    (stkentry)vm_current->pc);
			vm_current->pc += val;
			stack_pop(vm_current, 1);
			break;
		case OP_BZ:
		case OP_BZL:
			INTOP
			val = STACKTOP;
			stack_pop(vm_current, 1);
			if (val == 0) {
				if (opcode == OP_BZL)
					stack_push(vm_current,
					    (stkentry)vm_current->pc);
				vm_current->pc += intop;
			}
			break;
		case OP_BNZ:
		case OP_BNZL:
			INTOP
			val = STACKTOP;
			stack_pop(vm_current, 1);
			if (val != 0) {
				if (opcode == OP_BNZL)
					stack_push(vm_current,
					    (stkentry)vm_current->pc);
				vm_current->pc += intop;
			}
			break;
		case OP_RET:
			vm_current->pc = (instr *)STACKTOP;
			stack_pop(vm_current, 1);
			break;

		case OP_CALLNUM:
			INTOP
			if ((intop >=0) && (intop < vm_intfn_size)) {
				if (!vm_intfn_table[intop]()) {
					RESTART
				}
			} else
				printf("Internal function out of range\n");
			break;
		case OP_CALLSTR:
			INTOP
			vm_current->pc += intop;
			NOTIMPL
			break;

		case OP_ADD:
			val = STACKTOP;
			stack_pop(vm_current, 1);
			STACKTOP += val;
			break;
		case OP_SUB:
			val = STACKTOP;
			stack_pop(vm_current, 1);
			STACKTOP -= val;
			break;
		case OP_MUL:
			val = STACKTOP;
			stack_pop(vm_current, 1);
			STACKTOP *= val;
			break;
		case OP_DIV:
			val = STACKTOP;
			stack_pop(vm_current, 1);
			STACKTOP /= val;
			break;
		case OP_EQ:
			val = STACKTOP;
			stack_pop(vm_current, 1);
			STACKTOP = (STACKTOP == val);
			break;
		case OP_NE:
			val = STACKTOP;
			stack_pop(vm_current, 1);
			STACKTOP = (STACKTOP != val);
			break;
		case OP_LT:
			val = STACKTOP;
			stack_pop(vm_current, 1);
			STACKTOP = (STACKTOP < val);
			break;
		case OP_GT:
			val = STACKTOP;
			stack_pop(vm_current, 1);
			STACKTOP = (STACKTOP > val);
			break;
		case OP_LE:
			val = STACKTOP;
			stack_pop(vm_current, 1);
			STACKTOP = (STACKTOP <= val);
			break;
		case OP_GE:
			val = STACKTOP;
			stack_pop(vm_current, 1);
			STACKTOP = (STACKTOP >= val);
			break;

		case FLOP_ADD:
			flval = STACKTOPFL;
			stack_pop(vm_current, 1);
			STACKTOPFL += flval;
			break;
		case FLOP_SUB:
			flval = STACKTOPFL;
			stack_pop(vm_current, 1);
			STACKTOPFL -= flval;
			break;
		case FLOP_MUL:
			flval = STACKTOPFL;
			stack_pop(vm_current, 1);
			STACKTOPFL *= flval;
			break;
		case FLOP_DIV:
			flval = STACKTOPFL;
			stack_pop(vm_current, 1);
			STACKTOPFL /= flval;
			break;
		case FLOP_EQ:
			flval = STACKTOPFL;
			stack_pop(vm_current, 1);
			STACKTOP = (STACKTOPFL == flval);
			break;
		case FLOP_NE:
			flval = STACKTOPFL;
			stack_pop(vm_current, 1);
			STACKTOP = (STACKTOPFL != flval);
			break;
		case FLOP_LT:
			flval = STACKTOPFL;
			stack_pop(vm_current, 1);
			STACKTOP = (STACKTOPFL < flval);
			break;
		case FLOP_GT:
			flval = STACKTOPFL;
			stack_pop(vm_current, 1);
			STACKTOP = (STACKTOPFL > flval);
			break;
		case FLOP_LE:
			flval = STACKTOPFL;
			stack_pop(vm_current, 1);
			STACKTOP = (STACKTOPFL <= flval);
			break;
		case FLOP_GE:
			flval = STACKTOPFL;
			stack_pop(vm_current, 1);
			STACKTOP = (STACKTOPFL >= flval);
			break;

		default:
			printf("Unrecognised opcode\n");
			break;
		}
	}
}
