/* code.c */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <err.h>
#include <limits.h>
#include "types.h"
#include "ast.h"
#include "codegen.h"
#include "code.h"
#include "mem.h"
#include "hash.h"


struct var_desc {
	int type;
	int addr;
	int flags;
	char *name;
};

struct fn_desc {
	int type;
	int num;
	char *name;
};

struct label {
	int value;
	char *name;
};

#define DEBUG 1
#define MOREDEBUG 0

#define MAX_LABELS 4096
#define MAX_CONSTS 4096

struct label *labels = NULL;
int nlabels = 0;
int maxlabels = 0;

int *consts = NULL;
int nconsts = 0;
int maxconsts = 0;

FILE *binout = NULL;

int sp = 0;
int consume = 1;
int real = 0;
int switchmin;
int *switchlabels = NULL;
int switchdefault;
int breaklabel;

int output_asm = 1;

void output_int(int);
void output_byte(int);
void output_string(char *);
int get_label(int);

#define IF_ISINSTR	0x01
#define	IF_ISLABEL	0x02
#define IF_ADDLABEL	0x04
#define IF_ADDCONST	0x08
#define IF_HASOPERAND	0x10
#define IF_HASSTRING	0x20
#define IF_SUBPC	0x40
#define IF_FIXEDSIZE	0x80

struct instr {
	struct instr *next;
	int opcode;
	int operand;
	char *string;
	int label;
	int constant;
	int bytes;
	int flags;
};

struct instr *instr_head = NULL;
struct instr *instr_tail = NULL;

#define VAR_LOCAL 1
#define VAR_GLOBAL 2
#define VAR_ARRAY 4

#define VAR_ARG 1
#define VAR_REAL 2

#define FN_INT 1
#define FN_STR 2
#define FN_LOCAL 3

int fnconst;
int nargs;
int local_variable_count = -1;
int local_arg_count = -1;
int function_count = -1;
int constant_count = -1;

#define HASHSIZE 512
#define HASHMASK (HASHSIZE - 1)

struct hashentry *varhash[HASHSIZE];
struct hashentry *fnhash[HASHSIZE];
struct hashentry *constanthash[HASHSIZE];

void compiler_error(char *str)
{
	errx(1, str);
}

int count_local_variables(void)
{
	return local_variable_count;
}

void dump_local_variables_action(struct hashentry *ptr)
{
	printf("%%var %s %d %d\n", ptr->name, ptr->value, ptr->flags);
}

void dump_local_variables(void)
{
	hash_iterate(varhash, dump_local_variables_action);
}

void output_functions_action(struct hashentry *ptr)
{
	int len, pad;

	if (ptr->flags == FN_LOCAL) {
		len = strlen(ptr->name) + 1 + 16;
		pad = (4 - (len % 4)) % 4;
		output_int(len+pad);
		output_int(0);	/* type */
		output_int(get_label(ptr->value) + 8);
		output_int(0);	/* nargs */
		output_string(ptr->name);
		output_byte(0);	/* terminator */
		while (pad--)
			output_byte(0);
	}
}

void output_functions(void)
{
	hash_iterate(fnhash, output_functions_action);

	output_int(0);		/* terminator */
}

void reset_local_variables(void)
{
	if (local_variable_count >= 0)
		hash_clear(varhash);

	hash_init(varhash);

	local_variable_count = 0;
	local_arg_count = 0;
}

void reset_functions(void)
{
	if (function_count >= 0)
		hash_clear(fnhash);

	hash_init(fnhash);

	function_count = 0;
}

void reset_constants(void)
{
	if (constant_count >= 0)
		hash_clear(constanthash);

	hash_init(constanthash);

	constant_count = 0;
}

int lookup_variable(ast *node, struct var_desc *var, int create, int flags)
{
	char *varname;
	struct hashentry *hashptr;

	assert((node->tag == var_ast) || ((node->tag == array_ast) &&
	    (node->info.node.tag = kind_array)));

	var->type = 0;

	if (node->tag == var_ast)
		varname = node->info.variable;
	else {
		varname = node->info.node.head->elem->info.variable;
		var->type |= VAR_ARRAY;
	}

	assert(varname != NULL);
	assert(varname[0] != '\0');

	var->name = varname;

	if (local_variable_count < 0)
		reset_local_variables();


	switch (varname[0]) {
	case '$':
		var->type |= VAR_GLOBAL;
		var->flags = 0;
		break;
	case '%':
		flags |= VAR_REAL;
	default:
		var->type |= VAR_LOCAL;
		hashptr = hash_lookup(varhash, varname, create);

		if (!hashptr)
			return 0;

		if (hashptr->name == NULL) {
			hashptr->name = varname;
			hashptr->flags = flags;
			if (flags & VAR_ARG)
				hashptr->value = local_arg_count++;
			else
				hashptr->value = local_variable_count++;
		}

		var->addr = hashptr->value;
		var->flags = hashptr->flags;

		printf("var %s, flags %d\n", varname, var->flags);
		fflush(stdout);
		break;
	}
	return 1;
}

int lookup_function(ast *node, struct fn_desc *fn)
{
	char *fnname = node->info.string;
	struct hashentry *hashptr;

	fn->name = fnname;

	if (function_count < 0)
		reset_functions();

	hashptr = hash_lookup(fnhash, fnname, 0);

	if (!hashptr)
		return 0;

	fn->type = hashptr->flags;
	fn->num = hashptr->value;

	return 1;
}

int lookup_constant(ast *node, int *constant)
{
	char *constantname = node->info.string;
	struct hashentry *hashptr;

	if (constant_count < 0)
		reset_constants();

	hashptr = hash_lookup(constanthash, constantname, 0);

	if (!hashptr)
		return 0;

	*constant = hashptr->value;

	return 1;
}

void create_function(char *fnname, int type, int num)
{
	struct hashentry *hashptr;

	if (function_count < 0)
		reset_functions();

	hashptr = hash_lookup(fnhash, fnname, 1);

	if (hashptr->name)
		compiler_error("Function already exists");

	if (output_asm) {
		switch (type) {
		case FN_INT:
			printf("%%fnint %s %d\n", fnname, num);
			break;
		case FN_STR:
			printf("%%fnext %s\n", fnname);
			break;
		case FN_LOCAL:
			printf("%%fnlocal %s lab_%d\n", fnname, num);
			break;
		}
	}

	hashptr->name = fnname;
	hashptr->flags = type;
	hashptr->value = num;
}

void create_constant(char *constantname, int value)
{
	struct hashentry *hashptr;

	if (constant_count < 0)
		reset_constants();

	hashptr = hash_lookup(constanthash, constantname, 1);

	if (hashptr->name)
		compiler_error("Constant already defined");

	hashptr->name = constantname;
	hashptr->value = value;
}

int newlabel(char *name)
{
	if (nlabels >= maxlabels) {
		/* We need to allocate more label space */
		if (nlabels != 0) {
			/* Error for now */
			compiler_error("Too many labels");
		}
		maxlabels = MAX_LABELS;
		labels = safe_malloc(maxlabels * sizeof(struct label));
	}
	labels[nlabels].value = -1;
	labels[nlabels].name = name;

	return nlabels++;
}

int newconst(void)
{
	if (nconsts >= maxconsts) {
		/* We need to allocate more const space */
		if (nconsts != 0) {
			/* Error for now */
			compiler_error("Too many consts");
		}
		maxconsts = MAX_CONSTS;
		consts = safe_malloc(maxconsts * sizeof(int));
	}
	consts[nconsts] = 0;

	return nconsts++;
}

void set_label(int label, int value)
{
	labels[label].value = value;
}

void set_const(int constant, int value)
{
	if (output_asm)
		printf("%%const const_%d %d\n", constant, value);
	consts[constant] = value;
}

int get_label(int label)
{
	return labels[label].value;
}

int get_const(int constant)
{
	return consts[constant];
}

#define INSTR_ADD(i)	i->next = NULL;			\
			if (instr_tail)			\
				instr_tail->next = i;	\
			else				\
				instr_head = i;		\
			instr_tail = i


void emit_simple_instr(int opcode)
{
	struct instr *i;

	i = safe_malloc(sizeof(struct instr));
	i->opcode = opcode;
	i->bytes = 1;
	i->flags = IF_ISINSTR;

	if (output_asm)
		printf("\t%s\n", INSTR_NAME(opcode));

	INSTR_ADD(i);
}

void emit_instr_immediate(int opcode, int operand)
{
	struct instr *i;

	i = safe_malloc(sizeof(struct instr));
	i->opcode = opcode;
	i->operand = operand;
	i->bytes = 2;	/* Start at 2 */
	i->flags = IF_ISINSTR | IF_HASOPERAND;

	if (output_asm)
		printf("\t%s %d\n", INSTR_NAME(opcode), operand);

	INSTR_ADD(i);
}

void emit_instr_string(int opcode, char *string)
{
	struct instr *i;

	i = safe_malloc(sizeof(struct instr));
	i->opcode = opcode;
	i->string = string;
	i->bytes = strlen(string) + 2;
	i->flags = IF_ISINSTR | IF_HASSTRING;

	if (output_asm)
		printf("\t%s \"%s\"\n", INSTR_NAME(opcode), string);

	INSTR_ADD(i);
}

void emit_instr_imm_const(int opcode, int operand, int constant)
{
	struct instr *i;

	i = safe_malloc(sizeof(struct instr));
	i->opcode = opcode;
	i->operand = operand;
	i->constant = constant;
	i->bytes = 2;	/* Start at 2 */
	i->flags = IF_ISINSTR | IF_HASOPERAND | IF_ADDCONST;

	if (output_asm)
		printf("\t%s %d + const_%d\n", INSTR_NAME(opcode), operand,
		    constant);

	INSTR_ADD(i);
}

void emit_instr_label(int opcode, int label)
{
	struct instr *i;

	i = safe_malloc(sizeof(struct instr));
	i->opcode = opcode;
	i->operand = 0;
	i->label = label;
	i->bytes = 2;	/* Start at 2 */
	i->flags = IF_ISINSTR | IF_HASOPERAND | IF_ADDLABEL | IF_SUBPC;

	if (output_asm)
		printf("\t%s lab_%d\n", INSTR_NAME(opcode), label);

	INSTR_ADD(i);
}

void emit_instr_label_sized(int opcode, int label, int size)
{
	struct instr *i;

	i = safe_malloc(sizeof(struct instr));
	i->opcode = opcode;
	i->operand = 0;
	i->label = label;
	i->bytes = size;
	i->flags = IF_ISINSTR | IF_HASOPERAND | IF_ADDLABEL | IF_SUBPC
	    | IF_FIXEDSIZE;

	if (output_asm)
		printf("\t%s lab_%d\n", INSTR_NAME(opcode), label);

	INSTR_ADD(i);
}

void emit_label(int label)
{
	struct instr *i;

	i = safe_malloc(sizeof(struct instr));
	i->flags = IF_ISLABEL;
	i->label = label;
	i->bytes = 0;

	if (output_asm)
		printf("lab_%d:\n", label);

	INSTR_ADD(i);
}

void codegen(ast *node)
{
	struct var_desc var;
	struct fn_desc fn;
	int len;
	ast_list *ptr;
	int savedsp;
	int savedreal;
	int label, label2;
	int localconsume;
	int num;
	int max;
	int range;
	int savedswitchmin;
	int *savedswitchlabels;
	int savedswitchdefault;
	int savedbreaklabel;
	int switchend;
	int stackfixlabel;
	int hasdefault;
	int i;

	union {
		int i;
		float f;
	} conv;

#if DEBUG
	printf("entering codegen with node %p, sp = %d, tag = %d\n", node, sp, node->tag);
	if (node->tag == node_ast)
		printf("kind == %d\n", node->info.node.tag);
	fflush(stdout);
#endif

	switch (node->tag) {
	case int_ast:
		emit_instr_immediate(OP_PUSH, node->info.integer);
		real = 0;
		sp++;
		break;
	case real_ast:
		conv.f = node->info.real; /* XXX Not exactly MI */
		emit_instr_immediate(OP_PUSH, conv.i);
		real = 1;
		sp++;
		break;
	case casenum_ast:
		num = node->info.integer;
		num = num - switchmin;
		emit_label(switchlabels[num]);
		break;
	case casevar_ast:
		lookup_constant(node, &num);
		num = num - switchmin;
		emit_label(switchlabels[num]);
		break;
	case var_ast:
	case array_ast:
		if (!lookup_variable(node, &var, 0, 0)) {
			/*
			 * If the var doesn't exist, check for a constant
			 * If we find one, great. Deal with it, and get
			 * on with the rest of the tree. If not, bail.
			 */
			if (lookup_constant(node, &num)) {
				emit_instr_immediate(OP_PUSH, num);
				sp++;
				real = (*node->info.string == '%');
				break;
			}
			printf("error: variable '%s' used before assignment\n",
			    var.name);
			exit(EXIT_FAILURE);
		}
//		printf("sp = %d\n", sp);
		if (var.flags & VAR_REAL)
			real = 1;
		else
			real = 0;
		switch (var.type) {
		case VAR_LOCAL:
			if (var.flags & VAR_ARG) {
				/*
				 * Arguments on the stack directly above
				 * local variables, and then return address
				 */
				emit_instr_imm_const(OP_LOAD,
				    sp + 1 + nargs - var.addr, fnconst);
			} else {
				emit_instr_imm_const(OP_LOAD, sp - var.addr - 1, fnconst);
			}
			sp++;
			break;
		case VAR_LOCAL | VAR_ARRAY:
			compiler_error("Local arrays not supported");
			break;
		case VAR_GLOBAL:
			emit_instr_string(OP_PUSHSTR, var.name);
			emit_instr_immediate(OP_CALLNUM, INTFN_GLOBAL_LOAD);
			sp++;
			break;
		case VAR_GLOBAL | VAR_ARRAY:
			codegen(node->info.node.head->next->elem);
			emit_instr_string(OP_PUSHSTR, var.name);
			emit_instr_immediate(OP_CALLNUM,
			    INTFN_GLOBAL_ARRAY_LOAD);
			/* No sp++ because codegen has already done it */
			break;
		}
		break;
	case str_ast:
		len = strlen(node->info.string);
		emit_instr_string(OP_PUSHSTR, node->info.string);
		sp+=(len+sizeof(stkentry)-1) / sizeof(stkentry) + 1;
		break;
	case node_ast:
		switch (node->info.node.tag) {
		case kind_fndefint:
			assert(sp == 0);
			assert(node->info.node.head != NULL);
			assert(node->info.node.head->next != NULL);

			create_function(node->info.node.head->elem->info.string,
			    FN_INT,
			    node->info.node.head->next->elem->info.integer);
			break;
		case kind_constant:
			assert(sp == 0);
			assert(node->info.node.head != NULL);
			assert(node->info.node.head->next != NULL);

			create_constant(node->info.node.head->elem->info.string,
			    node->info.node.head->next->elem->info.integer);
			break;
		case kind_fndefext:
			assert(sp == 0);
			assert(node->info.node.head != NULL);

			create_function(node->info.node.head->elem->info.string,
			    FN_STR, 0);
			break;
		case kind_fndef:
			assert(sp == 0);
			assert(node->info.node.head != NULL);
			assert(node->info.node.head->next != NULL);
			assert(node->info.node.head->next->next != NULL);

			label =
			    newlabel(node->info.node.head->elem->info.string);
			emit_label(label);
			/* Set up the arguments in the symbol table */
			nargs = 0;
			for (ptr =
			    node->info.node.head->next->elem->info.node.head;
			    ptr; ptr = ptr->next) {
				lookup_variable(ptr->elem, &var, 1, VAR_ARG);
				nargs++;
			}

			/* Should store the number of args somewhere */
			create_function(node->info.node.head->elem->info.string,
				FN_LOCAL, label);

			/* Allocate space on stack for local variables */
			fnconst = newconst();
			emit_instr_imm_const(OP_ALLOC, 0, fnconst);
			/* Evaluate the contents */
			codegen(node->info.node.head->next->next->elem);
			/* Restore the stack and return */
			emit_instr_imm_const(OP_POP, 0, fnconst);
			emit_simple_instr(OP_RET);
			/* And set the constant */
			set_const(fnconst, count_local_variables());
			if (output_asm)
				dump_local_variables();
			reset_local_variables();
			break;
		case kind_assign:
			/* We have a list with an lvalue and an expression */
			assert(node->info.node.head != NULL);
			assert(node->info.node.head->next != NULL);
			assert(node->info.node.head->next->next == NULL);

			ptr = node->info.node.head->elem->info.node.head;

			consume = 0;
			while (ptr) {
				consume++;
				ptr = ptr->next;
			}
			if (consume < 1)
				consume = 1;

			/* Evaluate the expression first */
			codegen(node->info.node.head->next->elem);

			ptr = node->info.node.head->elem->info.node.head;
			if (ptr == NULL) {
				/* Consume expression */
				emit_instr_immediate(OP_POP, 1);
				sp--;
			}
			while (ptr) {
				lookup_variable(ptr->elem, &var, 1, 0);
				switch (var.type) {
				case VAR_LOCAL:
					emit_instr_imm_const(OP_STORE,
					    sp - var.addr - 2, fnconst);
					sp--;
					break;
				case VAR_LOCAL | VAR_ARRAY:
					compiler_error("Local arrays not supported");
					break;
				case VAR_GLOBAL:
					emit_instr_string(OP_PUSHSTR, var.name);
					emit_instr_immediate(OP_CALLNUM,
					    INTFN_GLOBAL_STORE);
					sp--;
					break;
				case VAR_GLOBAL | VAR_ARRAY:
					codegen(ptr->elem->info.node.head->next->elem);
					emit_instr_string(OP_PUSHSTR, var.name);
					emit_instr_immediate(OP_CALLNUM,
					    INTFN_GLOBAL_ARRAY_STORE);
					sp--;
					sp--;
					break;
				}
				ptr = ptr->next;
			}
			break;
		case kind_list:
			for (ptr = node->info.node.head; ptr; ptr = ptr->next)
				codegen(ptr->elem);
			break;
		case kind_call:
			/* We have a function name and a list of arguments */
			assert(node->info.node.head != NULL);

			savedsp = sp;
			localconsume = consume;
			consume = 1;

//			printf("savedsp = %d\n", savedsp);

			/* Evaluate the arguments first */
			for (ptr =
			    node->info.node.head->next->elem->info.node.head;
			    ptr; ptr = ptr->next) {
				codegen(ptr->elem);
			}

//			printf("sp = %d\n", sp);

			emit_instr_immediate(OP_ALLOC, 1);
			sp++;

			if (!lookup_function(node->info.node.head->elem, &fn))
				compiler_error("Function not found");
			switch (fn.type) {
			case FN_INT:
				emit_instr_immediate(OP_CALLNUM, fn.num);
				break;
			case FN_STR:
				emit_instr_string(OP_CALLSTR, fn.name);
				break;
			case FN_LOCAL:
				emit_instr_label(OP_BL, fn.num);
				break;
			}
//			printf("sp = %d, savedsp = %d\n", sp, savedsp);
			if (sp > (savedsp + localconsume)) {
				emit_instr_immediate(OP_STORE, sp - savedsp
				    - localconsume - 1);
				if (sp > (savedsp + localconsume + 1)) {
					emit_instr_immediate(OP_POP,
					    sp-savedsp - localconsume - 1);
				}
			}
//			printf("localconsume = %d\n", localconsume);
			sp = savedsp + localconsume;
			break;

#define BINOP		assert(node->info.node.head != NULL);		\
			assert(node->info.node.head->next != NULL);	\
			assert(node->info.node.head->next->next == NULL); \
			codegen(node->info.node.head->elem);		\
			savedreal = real;				\
			codegen(node->info.node.head->next->elem);	\
			if (savedreal != real)				\
				compiler_error("Type mismatch");
			
		case op_plus:
			BINOP
			if (real)
				emit_simple_instr(FLOP_ADD);
			else
				emit_simple_instr(OP_ADD);
			sp--;
			break;
		case op_minus:
			assert(node->info.node.head != NULL);
			if (node->info.node.head->next == NULL) {
				emit_instr_immediate(OP_PUSH, 0);
				sp++;
			}
			codegen(node->info.node.head->elem);
			savedreal = real;
			if (node->info.node.head->next != NULL)
				codegen(node->info.node.head->next->elem);
			if (savedreal != real)
				compiler_error("Type mismatch");
			
			if (real)
				emit_simple_instr(FLOP_SUB);
			else
				emit_simple_instr(OP_SUB);
			sp--;
			break;
		case op_times:
			BINOP
			if (real)
				emit_simple_instr(FLOP_MUL);
			else
				emit_simple_instr(OP_MUL);
			sp--;
			break;
		case op_divide:
			BINOP
			if (real)
				emit_simple_instr(FLOP_DIV);
			else
				emit_simple_instr(OP_DIV);
			sp--;
			break;
		case op_gt:
			BINOP
			if (real)
				emit_simple_instr(FLOP_GT);
			else
				emit_simple_instr(OP_GT);
			real = 0;
			sp--;
			break;
		case op_lt:
			BINOP
			if (real)
				emit_simple_instr(FLOP_LT);
			else
				emit_simple_instr(OP_LT);
			real = 0;
			sp--;
			break;
		case op_ge:
			BINOP
			if (real)
				emit_simple_instr(FLOP_GE);
			else
				emit_simple_instr(OP_GE);
			real = 0;
			sp--;
			break;
		case op_le:
			BINOP
			if (real)
				emit_simple_instr(FLOP_LE);
			else
				emit_simple_instr(OP_LE);
			real = 0;
			sp--;
			break;
		case op_eq:
			BINOP
			if (real)
				emit_simple_instr(FLOP_EQ);
			else
				emit_simple_instr(OP_EQ);
			real = 0;
			sp--;
			break;
		case op_ne:
			BINOP
			if (real)
				emit_simple_instr(FLOP_NE);
			else
				emit_simple_instr(OP_NE);
			real = 0;
			sp--;
			break;
		case op_and:
			assert(node->info.node.head != NULL);
			assert(node->info.node.head->next != NULL);
			assert(node->info.node.head->next->next == NULL);
			label = newlabel(NULL);
			label2 = newlabel(NULL);
			codegen(node->info.node.head->elem);
			if (real)
				compiler_error("Type mismatch");
			emit_instr_label(OP_BZ, label);
			sp--;
			codegen(node->info.node.head->next->elem);
			if (real)
				compiler_error("Type mismatch");
			emit_instr_label(OP_BZ, label);
			emit_instr_immediate(OP_PUSH, 1);
			emit_instr_label(OP_B, label2);
			emit_label(label);
			emit_instr_immediate(OP_PUSH, 0);
			emit_label(label2);
			break;
		case op_or:
			assert(node->info.node.head != NULL);
			assert(node->info.node.head->next != NULL);
			assert(node->info.node.head->next->next == NULL);
			label = newlabel(NULL);
			label2 = newlabel(NULL);
			codegen(node->info.node.head->elem);
			if (real)
				compiler_error("Type mismatch");
			emit_instr_label(OP_BNZ, label);
			sp--;
			codegen(node->info.node.head->next->elem);
			if (real)
				compiler_error("Type mismatch");
			emit_instr_label(OP_BNZ, label);
			emit_instr_immediate(OP_PUSH, 0);
			emit_instr_label(OP_B, label2);
			emit_label(label);
			emit_instr_immediate(OP_PUSH, 1);
			emit_label(label2);
			break;
		case stmt_if:
			assert(node->info.node.head != NULL);
			assert(node->info.node.head->next != NULL);
			if (node->info.node.head->next->next)
				assert(node->info.node.head->next->next->next
				    == NULL);

			/* Evaluate condition first */
			codegen(node->info.node.head->elem);
			assert(real == 0);
			label = newlabel(NULL); /* else */
			label2 = newlabel(NULL); /* end of else */
			emit_instr_label(OP_BZ, label);
			sp--;
			savedsp = sp;
			/* Then evaluate the "if" code */
			codegen(node->info.node.head->next->elem);
//			printf("sp = %d, savedsp = %d\n", sp, savedsp);
			assert(sp == savedsp);
			if (node->info.node.head->next->next) {
				emit_instr_label(OP_B, label2);
				emit_label(label);
				codegen(node->info.node.head->next->next->elem);
			} else {
				emit_label(label);
			}
			emit_label(label2);
			/* Then, if we had an else, evaluate it here */
			break;
		case stmt_while:
			assert(node->info.node.head != NULL);
			assert(node->info.node.head->next != NULL);
			assert(node->info.node.head->next->next == NULL);

			/* Label at the start of the loop */
			label = newlabel(NULL);
			emit_label(label);
			/* Evaluate the condition */
			codegen(node->info.node.head->elem);
			assert(real == 0);
			/* Exit loop if condition evaluates to false */
			label2 = newlabel(NULL);
			savedbreaklabel = breaklabel;
			breaklabel = label2;
			emit_instr_label(OP_BZ, label2);
			sp--;
			/* Evaluate the loop interior */
			codegen(node->info.node.head->next->elem);
			/* Branch to the beginning of the loop */
			emit_instr_label(OP_B, label);
			/* Exit point */
			emit_label(label2);
			breaklabel = savedbreaklabel;
			break;
		case stmt_switch:
			assert(node->info.node.head != NULL);
			assert(node->info.node.head->next != NULL);
			assert(node->info.node.head->next->next == NULL);
			/* Verify constants, and calculate min and max */
			savedswitchmin = switchmin;
			savedswitchlabels = switchlabels; 
			savedswitchdefault = switchdefault; 
			savedbreaklabel = breaklabel;
			switchmin = INT_MAX;
			max = 0;
			for (ptr =
			    node->info.node.head->next->elem->info.node.head;
			    ptr; ptr = ptr->next) {
				num = 0;
				switch (ptr->elem->tag) {
				case casenum_ast:
					num = ptr->elem->info.integer;
					if (num > max)
						max = num;
					if (num < switchmin)
						switchmin = num;
					break;
				case casevar_ast:
					if (!lookup_constant(ptr->elem, &num))
					    compiler_error("Not a constant");
					if (num > max)
						max = num;
					if (num < switchmin)
						switchmin = num;
					break;
				default:
					break;
				}
			}
			if (switchmin > max)
				switchmin = max;
			range = max - switchmin;
			if (range > 1024)
			  compiler_error("Excessive range in switch statement");
			printf("Switch min %d max %d range %d\n", switchmin, max, range);
			switchlabels = safe_malloc(sizeof(int)*(range+1));
			switchdefault = newlabel(NULL);
			for (i = 0; i <= range; i++)
				switchlabels[i] = switchdefault;
			/*
			 * Allocate labels for all used cases, and set
			 * the rest to default
			 */
			hasdefault = 0;
			for (ptr =
			    node->info.node.head->next->elem->info.node.head;
			    ptr; ptr = ptr->next) {
				switch (ptr->elem->tag) {
				case casenum_ast:
					num = ptr->elem->info.integer;
					switchlabels[num-switchmin] =
					    newlabel(NULL);
					break;
				case casevar_ast:
					lookup_constant(ptr->elem, &num);
					switchlabels[num-switchmin] =
					    newlabel(NULL);
					break;
				case node_ast:
					if (ptr->elem->info.node.tag ==
					    stmt_default)
						hasdefault = 1;
				default:
					break;
				}
			}
			switchend = newlabel(NULL);
			stackfixlabel = newlabel(NULL);
			breaklabel = switchend;

			/* Evaluate condition */
			codegen(node->info.node.head->elem);
			if (switchmin < 0) {
				emit_instr_immediate(OP_PUSH, -switchmin);
				emit_simple_instr(OP_ADD);
			}
			if (switchmin > 0) {
				emit_instr_immediate(OP_PUSH, switchmin);
				emit_simple_instr(OP_SUB);
			}
			/* Range check */
			emit_instr_immediate(OP_LOAD, 0);
			emit_instr_immediate(OP_PUSH, 0);
			emit_simple_instr(OP_LT);
			emit_instr_label(OP_BNZ, stackfixlabel);
			emit_instr_immediate(OP_LOAD, 0);
			emit_instr_immediate(OP_PUSH, range);
			emit_simple_instr(OP_GT);
			emit_instr_label(OP_BNZ, stackfixlabel);

			/* Emit branch table */
			emit_instr_immediate(OP_PUSH, 3);
			emit_simple_instr(OP_MUL);
			emit_simple_instr(OP_BI);
			for (i = 0; i <= range; i++)
				/*
				 * We must constrain branch table entries to
				 * a fixed size
				 */
				emit_instr_label_sized(OP_B, switchlabels[i],
				    3);

			emit_label(stackfixlabel);
			emit_instr_immediate(OP_POP, 1);
			emit_instr_label(OP_B, switchdefault);

			sp--;
			printf("sp = %d\n", sp);

			/* Evaluate all cases */
			codegen(node->info.node.head->next->elem);

			printf("sp = %d\n", sp);

			if (!hasdefault)
				emit_label(switchdefault);
			emit_label(switchend);
			free(switchlabels);
			switchmin = savedswitchmin;
			switchlabels = savedswitchlabels;
			switchdefault = savedswitchdefault;
			breaklabel = savedbreaklabel;
			break;
		case stmt_default:
			emit_label(switchdefault);
			break;
		case stmt_break:
			emit_instr_label(OP_B, breaklabel);
			break;
		case stmt_return:
			assert(node->info.node.head == NULL);

			emit_instr_imm_const(OP_POP, sp, fnconst);
			emit_simple_instr(OP_RET);
			break;
		default:
			printf("unknown list type:\n");
			break;
		}

		break;
	default:
		printf("unknown node type\n");
		break;
	}
}

void output_byte(int byte)
{
#if MOREDEBUG
	printf("Output byte: %x\n", byte);
#endif
	fputc(byte, binout);
}

void output_int(int num)
{
#if MOREDEBUG
	printf("Output int: %x\n", num);
#endif
	output_byte((num >> 24) & 0xff);
	output_byte((num >> 16) & 0xff);
	output_byte((num >> 8) & 0xff);
	output_byte(num & 0xff);
}

void output_string(char *string)
{
#if MOREDEBUG
	printf("Output string: \"%s\"\n", string);
#endif
	fputs(string, binout);
}

int bytesmask[4] = { 0xff, 0xffff, 0xffffff, 0xffffffff };

#define BYTESMASK(bytes)	(bytesmask[bytes])

void output_code(void)
{
	struct instr *ptr;
	int morepasses = 1;
	int passno = 0;
	int pc;

	while (morepasses) {
		ptr = instr_head;
		pc = 0;
		morepasses = 0;
#if DEBUG
		printf("Starting pass %d\n", passno);
#endif
		while (ptr) {
			if (ptr->flags & IF_ISLABEL)
				if (get_label(ptr->label) != pc) {
					set_label(ptr->label, pc);
#if DEBUG
					printf("New pass (label) at PC = %d\n", pc);
#endif
					morepasses = 1;
				}
			if (ptr->flags & IF_ISINSTR)
				pc += ptr->bytes;
			if ((passno > 0) && (ptr->flags & IF_HASOPERAND)) {
				int operand;
				operand = ptr->operand;
				if (ptr->flags & IF_ADDCONST)
					operand += get_const(ptr->constant);
				if (ptr->flags & IF_ADDLABEL)
					operand += get_label(ptr->label);
				if (ptr->flags & IF_SUBPC)
					operand -= pc;
				assert(ptr->bytes <= 5);
#if MOREDEBUG
				printf("operand = %x, bytes = %d, mask = %x\n", operand, ptr->bytes-2, BYTESMASK(ptr->bytes-2));
#endif
				while ((operand & BYTESMASK(ptr->bytes-2)) !=
				    operand) {
					if ((!morepasses) && 
					    (ptr->flags & IF_FIXEDSIZE)) {
					  fprintf(stderr, "Label out of range: lab_%d: %d\n", ptr->label, operand);
					  compiler_error("Label out of range");
					}
					if (!(ptr->flags & IF_FIXEDSIZE)) {
						ptr->bytes++;
						pc++;
						morepasses = 1;
#if DEBUG
						printf("New pass at PC = %d\n", pc);
#endif
					} else {
						break;
					}
					assert(ptr->bytes <= 5);
				}
			}
			ptr = ptr->next;
		}
		passno++;
#if DEBUG
		printf("End of pass pc = %d\n", pc);
#endif
	}

#if DEBUG
	printf("Finished passes.. outputting code\n");
#endif

	/*
	 * Code is relocatable. Output function table, and fix up 
	 * pointers by the size of the header.
	*/

	output_byte(MAGIC1);
	output_byte(MAGIC2);
	output_byte(VERSION1);
	output_byte(VERSION2);

	pc = (pc + 3) & ~3;	/* Align table */

	output_int(pc+8);	/* Pointer to function table */

	ptr = instr_head;
	pc = 0;
	while (ptr) {
		if (ptr->flags & IF_ISINSTR) {
			int bytes;
			bytes = ptr->bytes;
			pc+=ptr->bytes;
			bytes--;
			if (ptr->flags & IF_HASOPERAND) {
				int operand;
#if MOREDEBUG
				printf("bytes = %d\n", bytes);
#endif
				output_byte(ptr->opcode | SET_BYTECOUNT(bytes));
				operand = ptr->operand;
				if (ptr->flags & IF_ADDCONST)
					operand += get_const(ptr->constant);
				if (ptr->flags & IF_ADDLABEL)
					operand += get_label(ptr->label);
				if (ptr->flags & IF_SUBPC)
					operand -= pc;
				while (bytes) {
					output_byte((operand >> (8*(bytes-1)))
					    & 0xff);
					bytes--;
				}
			} else if (ptr->flags & IF_HASSTRING) {
				int len;
				len = strlen(ptr->string);
				bytes -= len;
				output_byte(ptr->opcode | SET_BYTECOUNT(bytes));
				while (bytes) {
					output_byte((len >> (8*(bytes-1)))
					    & 0xff);
					bytes--;
				}
				output_string(ptr->string);
			} else {
				output_byte(ptr->opcode);
			}
		}
		ptr = ptr->next;
	}
	while (pc % 4) {
		output_byte(0);
		pc++;
	}
	output_functions();
}
