/*
 * sparse/compile-i386.c
 *
 * Copyright (C) 2003 Transmeta Corp.
 *               2003 Linus Torvalds
 * Copyright 2003 Jeff Garzik
 *
 * Licensed under the Open Software License version 1.1
 *
 * x86 backend
 *
 * TODO list:
 * in general, any non-32bit SYM_BASETYPE is unlikely to work.
 * complex initializers
 * bitfields
 * global struct/union variables
 * addressing structures, and members of structures (as opposed to
 *     scalars) on the stack.  Requires smarter stack frame allocation.
 * labels / goto
 * any function argument that isn't 32 bits (or promoted to such)
 * inline asm
 * floating point
 *
 */
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>

#include "lib.h"
#include "allocate.h"
#include "token.h"
#include "parse.h"
#include "symbol.h"
#include "scope.h"
#include "expression.h"
#include "target.h"
#include "compile.h"
#include "bitmap.h"

struct textbuf {
	unsigned int	len;	/* does NOT include terminating null */
	char		*text;
	struct textbuf	*next;
	struct textbuf	*prev;
};

struct loop_stack {
	int		continue_lbl;
	int		loop_bottom_lbl;
	struct loop_stack *next;
};

struct atom;
struct storage;
DECLARE_PTR_LIST(str_list, struct atom);
DECLARE_PTR_LIST(atom_list, struct atom);
DECLARE_PTR_LIST(storage_list, struct storage);

struct function {
	int stack_size;
	int pseudo_nr;
	struct storage_list *pseudo_list;
	struct atom_list *atom_list;
	struct str_list *str_list;
	struct loop_stack *loop_stack;
	struct symbol **argv;
	unsigned int argc;
	int ret_target;
};

enum storage_type {
	STOR_PSEUDO,	/* variable stored on the stack */
	STOR_ARG,	/* function argument */
	STOR_SYM,	/* a symbol we can directly ref in the asm */
	STOR_REG,	/* scratch register */
	STOR_VALUE,	/* integer constant */
	STOR_LABEL,	/* label / jump target */
	STOR_LABELSYM,	/* label generated from symbol's pointer value */
};

struct reg_info {
	const char	*name;
	struct storage	*contains;
	const unsigned char aliases[12];
#define own_regno aliases[0]
};

struct storage {
	enum storage_type type;
	unsigned long flags;

	/* STOR_REG */
	struct reg_info *reg;
	struct symbol *ctype;

	union {
		/* STOR_PSEUDO */
		struct {
			int pseudo;
			int offset;
			int size;
		};
		/* STOR_ARG */
		struct {
			int idx;
		};
		/* STOR_SYM */
		struct {
			struct symbol *sym;
		};
		/* STOR_VALUE */
		struct {
			long long value;
		};
		/* STOR_LABEL */
		struct {
			int label;
		};
		/* STOR_LABELSYM */
		struct {
			struct symbol *labelsym;
		};
	};
};

enum {
	STOR_LABEL_VAL	= (1 << 0),
	STOR_WANTS_FREE	= (1 << 1),
};

struct symbol_private {
	struct storage *addr;
};

enum atom_type {
	ATOM_TEXT,
	ATOM_INSN,
	ATOM_CSTR,
};

struct atom {
	enum atom_type type;
	union {
		/* stuff for text */
		struct {
			char *text;
			unsigned int text_len;  /* w/o terminating null */
		};

		/* stuff for insns */
		struct {
			char insn[32];
			char comment[40];
			struct storage *op1;
			struct storage *op2;
		};

		/* stuff for C strings */
		struct {
			struct string *string;
			int label;
		};
	};
};


static struct function *current_func = NULL;
static struct textbuf *unit_post_text = NULL;
static const char *current_section;

static void emit_comment(SCTX_ const char * fmt, ...) FORMAT_ATTR(1+SCTXCNT);
static void emit_move(SCTX_ struct storage *src, struct storage *dest,
		      struct symbol *ctype, const char *comment);
static int type_is_signed(SCTX_ struct symbol *sym);
static struct storage *x86_address_gen(SCTX_ struct expression *expr);
static struct storage *x86_symbol_expr(SCTX_ struct symbol *sym);
static void x86_symbol(SCTX_ struct symbol *sym);
static struct storage *x86_statement(SCTX_ struct statement *stmt);
static struct storage *x86_expression(SCTX_ struct expression *expr);

enum registers {
	NOREG,
	 AL,  DL,  CL,  BL,  AH,  DH,  CH,  BH,	// 8-bit
	 AX,  DX,  CX,  BX,  SI,  DI,  BP,  SP,	// 16-bit
	EAX, EDX, ECX, EBX, ESI, EDI, EBP, ESP,	// 32-bit
	EAX_EDX, ECX_EBX, ESI_EDI,		// 64-bit
};

/* This works on regno's, reg_info's and hardreg_storage's */
#define byte_reg(reg) ((reg) - 16)
#define highbyte_reg(reg) ((reg)-12)
#define word_reg(reg) ((reg)-8)

#define REGINFO(nr, str, conflicts...)	[nr] = { .name = str, .aliases = { nr , conflicts } }

static struct reg_info reg_info_table[] = {
	REGINFO( AL,  "%al", AX, EAX, EAX_EDX),
	REGINFO( DL,  "%dl", DX, EDX, EAX_EDX),
	REGINFO( CL,  "%cl", CX, ECX, ECX_EBX),
	REGINFO( BL,  "%bl", BX, EBX, ECX_EBX),
	REGINFO( AH,  "%ah", AX, EAX, EAX_EDX),
	REGINFO( DH,  "%dh", DX, EDX, EAX_EDX),
	REGINFO( CH,  "%ch", CX, ECX, ECX_EBX),
	REGINFO( BH,  "%bh", BX, EBX, ECX_EBX),
	REGINFO( AX,  "%ax", AL, AH, EAX, EAX_EDX),
	REGINFO( DX,  "%dx", DL, DH, EDX, EAX_EDX),
	REGINFO( CX,  "%cx", CL, CH, ECX, ECX_EBX),
	REGINFO( BX,  "%bx", BL, BH, EBX, ECX_EBX),
	REGINFO( SI,  "%si", ESI, ESI_EDI),
	REGINFO( DI,  "%di", EDI, ESI_EDI),
	REGINFO( BP,  "%bp", EBP),
	REGINFO( SP,  "%sp", ESP),
	REGINFO(EAX, "%eax", AL, AH, AX, EAX_EDX),
	REGINFO(EDX, "%edx", DL, DH, DX, EAX_EDX),
	REGINFO(ECX, "%ecx", CL, CH, CX, ECX_EBX),
	REGINFO(EBX, "%ebx", BL, BH, BX, ECX_EBX),
	REGINFO(ESI, "%esi", SI, ESI_EDI),
	REGINFO(EDI, "%edi", DI, ESI_EDI),
	REGINFO(EBP, "%ebp", BP),
	REGINFO(ESP, "%esp", SP),
	REGINFO(EAX_EDX, "%eax:%edx", AL, AH, AX, EAX, DL, DH, DX, EDX),
	REGINFO(ECX_EBX, "%ecx:%ebx", CL, CH, CX, ECX, BL, BH, BX, EBX),
	REGINFO(ESI_EDI, "%esi:%edi", SI, ESI, DI, EDI),
};

#define REGSTORAGE(nr) [nr] = { .type = STOR_REG, .reg = reg_info_table + (nr) }

static struct storage hardreg_storage_table[] = {
	REGSTORAGE(AL), REGSTORAGE(DL), REGSTORAGE(CL), REGSTORAGE(BL),
	REGSTORAGE(AH), REGSTORAGE(DH), REGSTORAGE(CH), REGSTORAGE(BH),
	REGSTORAGE(AX), REGSTORAGE(DX), REGSTORAGE(CX), REGSTORAGE(BX),
	REGSTORAGE(SI), REGSTORAGE(DI), REGSTORAGE(BP), REGSTORAGE(SP),
	REGSTORAGE(EAX), REGSTORAGE(EDX), REGSTORAGE(ECX), REGSTORAGE(EBX),
	REGSTORAGE(ESI), REGSTORAGE(EDI), REGSTORAGE(EBP), REGSTORAGE(ESP),
	REGSTORAGE(EAX_EDX), REGSTORAGE(ECX_EBX), REGSTORAGE(ESI_EDI),
};

#define REG_EAX (&hardreg_storage_table[EAX])
#define REG_ECX (&hardreg_storage_table[ECX])
#define REG_EDX (&hardreg_storage_table[EDX])
#define REG_ESP (&hardreg_storage_table[ESP])
#define REG_DL	(&hardreg_storage_table[DL])
#define REG_DX	(&hardreg_storage_table[DX])
#define REG_AL	(&hardreg_storage_table[AL])
#define REG_AX	(&hardreg_storage_table[AX])

static DECLARE_BITMAP(regs_in_use, 256);

static inline struct storage * reginfo_reg(struct reg_info *info)
{
	return hardreg_storage_table + info->own_regno;
}

static struct storage * get_hardreg(SCTX_ struct storage *reg, int clear)
{
	struct reg_info *info = reg->reg;
	const unsigned char *aliases;
	int regno;

	aliases = info->aliases;
	while ((regno = *aliases++) != NOREG) {
		if (test_bit(regno, regs_in_use))
			goto busy;
		if (clear)
			reg_info_table[regno].contains = NULL;
	}
	set_bit(info->own_regno, regs_in_use);
	return reg;
busy:
	fprintf(stderr, "register %s is busy\n", info->name);
	if (regno + reg_info_table != info)
		fprintf(stderr, "  conflicts with %s\n", reg_info_table[regno].name);
	exit(1);
}

static void put_reg(SCTX_ struct storage *reg)
{
	struct reg_info *info = reg->reg;
	int regno = info->own_regno;

	if (test_and_clear_bit(regno, regs_in_use))
		return;
	fprintf(stderr, "freeing already free'd register %s\n", reg_info_table[regno].name);
}

struct regclass {
	const char *name;
	const unsigned char regs[30];
};

static struct regclass regclass_8 = { "8-bit", { AL, DL, CL, BL, AH, DH, CH, BH }};
static struct regclass regclass_16 = { "16-bit", { AX, DX, CX, BX, SI, DI, BP }};
static struct regclass regclass_32 = { "32-bit", { EAX, EDX, ECX, EBX, ESI, EDI, EBP }};
static struct regclass regclass_64 = { "64-bit", { EAX_EDX, ECX_EBX, ESI_EDI }};

static struct regclass regclass_32_8 = { "32-bit bytes", { EAX, EDX, ECX, EBX }};

static struct regclass *get_regclass_bits(SCTX_ int bits)
{
	switch (bits) {
	case 8: return &regclass_8;
	case 16: return &regclass_16;
	case 64: return &regclass_64;
	default: return &regclass_32;
	}
}

static struct regclass *get_regclass(SCTX_ struct expression *expr)
{
	return get_regclass_bits(sctx_ expr->ctype->bit_size);
}

static int register_busy(SCTX_ int regno)
{
	if (!test_bit(regno, regs_in_use)) {
		struct reg_info *info = reg_info_table + regno;
		const unsigned char *regs = info->aliases+1;

		while ((regno = *regs) != NOREG) {
			regs++;
			if (test_bit(regno, regs_in_use))
				goto busy;
		}
		return 0;
	}
busy:
	return 1;
}

static struct storage *get_reg(SCTX_ struct regclass *class)
{
	const unsigned char *regs = class->regs;
	int regno;

	while ((regno = *regs) != NOREG) {
		regs++;
		if (register_busy(sctx_ regno))
			continue;
		return get_hardreg(sctx_ hardreg_storage_table + regno, 1);
	}
	fprintf(stderr, "Ran out of %s registers\n", class->name);
	exit(1);
}

static struct storage *get_reg_value(SCTX_ struct storage *value, struct regclass *class)
{
	struct reg_info *info;
	struct storage *reg;

	/* Do we already have it somewhere */
	info = value->reg;
	if (info && info->contains == value) {
		emit_comment(sctx_ "already have register %s", info->name);
		return get_hardreg(sctx_ hardreg_storage_table + info->own_regno, 0);
	}

	reg = get_reg(sctx_ class);
	emit_move(sctx_ value, reg, value->ctype, "reload register");
	info = reg->reg;
	info->contains = value;
	value->reg = info;
	return reg;
}

static struct storage *temp_from_bits(SCTX_ unsigned int bit_size)
{
	return get_reg(sctx_ get_regclass_bits(sctx_ bit_size));
}

static inline unsigned int pseudo_offset(struct storage *s)
{
	if (s->type != STOR_PSEUDO)
		return 123456;	/* intentionally bogus value */

	return s->offset;
}

static inline unsigned int arg_offset(struct storage *s)
{
	if (s->type != STOR_ARG)
		return 123456;	/* intentionally bogus value */

	/* FIXME: this is wrong wrong wrong */
	return current_func->stack_size + ((1 + s->idx) * 4);
}

static const char *pretty_offset(SCTX_ int ofs)
{
	static char esp_buf[64];

	if (ofs)
		sprintf(esp_buf, "%d(%%esp)", ofs);
	else
		strcpy(esp_buf, "(%esp)");

	return esp_buf;
}

static void stor_sym_init(SCTX_ struct symbol *sym)
{
	struct storage *stor;
	struct symbol_private *priv;

	priv = calloc(1, sizeof(*priv) + sizeof(*stor));
	if (!priv)
		sparse_die(sctx_ "OOM in stor_sym_init");

	stor = (struct storage *) (priv + 1);

	priv->addr = stor;
	stor->type = STOR_SYM;
	stor->sym = sym;
}

static const char *stor_op_name(SCTX_ struct storage *s)
{
	static char name[32];

	switch (s->type) {
	case STOR_PSEUDO:
		strcpy(name, pretty_offset(sctx_ (int) pseudo_offset(s)));
		break;
	case STOR_ARG:
		strcpy(name, pretty_offset(sctx_ (int) arg_offset(s)));
		break;
	case STOR_SYM:
		strcpy(name, show_ident(sctx_ s->sym->ident));
		break;
	case STOR_REG:
		strcpy(name, s->reg->name);
		break;
	case STOR_VALUE:
		sprintf(name, "$%Ld", s->value);
		break;
	case STOR_LABEL:
		sprintf(name, "%s.L%d", s->flags & STOR_LABEL_VAL ? "$" : "",
			s->label);
		break;
	case STOR_LABELSYM:
		sprintf(name, "%s.LS%p", s->flags & STOR_LABEL_VAL ? "$" : "",
			s->labelsym);
		break;
	}

	return name;
}

static struct atom *new_atom(SCTX_ enum atom_type type)
{
	struct atom *atom;

	atom = calloc(1, sizeof(*atom));	/* TODO: chunked alloc */
	if (!atom)
		sparse_die(sctx_ "nuclear OOM");

	atom->type = type;

	return atom;
}

static inline void push_cstring(SCTX_ struct function *f, struct string *str,
				int label)
{
	struct atom *atom;

	atom = new_atom(sctx_ ATOM_CSTR);
	atom->string = str;
	atom->label = label;

	add_ptr_list(&f->str_list, atom);	/* note: _not_ atom_list */
}

static inline void push_atom(SCTX_ struct function *f, struct atom *atom)
{
	add_ptr_list(&f->atom_list, atom);
}

static void push_text_atom(SCTX_ struct function *f, const char *text)
{
	struct atom *atom = new_atom(sctx_ ATOM_TEXT);

	atom->text = strdup(text);
	atom->text_len = strlen(text);

	push_atom(sctx_ f, atom);
}

static struct storage *new_storage(SCTX_ enum storage_type type)
{
	struct storage *stor;

	stor = calloc(1, sizeof(*stor));
	if (!stor)
		sparse_die(sctx_ "OOM in new_storage");

	stor->type = type;

	return stor;
}

static struct storage *stack_alloc(SCTX_ int n_bytes)
{
	struct function *f = current_func;
	struct storage *stor;

	assert(f != NULL);

	stor = new_storage(sctx_ STOR_PSEUDO);
	stor->type = STOR_PSEUDO;
	stor->pseudo = f->pseudo_nr;
	stor->offset = f->stack_size; /* FIXME: stack req. natural align */
	stor->size = n_bytes;
	f->stack_size += n_bytes;
	f->pseudo_nr++;

	add_ptr_list(&f->pseudo_list, stor);

	return stor;
}

static struct storage *new_labelsym(SCTX_ struct symbol *sym)
{
	struct storage *stor;

	stor = new_storage(sctx_ STOR_LABELSYM);

	if (stor) {
		stor->flags |= STOR_WANTS_FREE;
		stor->labelsym = sym;
	}

	return stor;
}

static struct storage *new_val(SCTX_ long long value)
{
	struct storage *stor;

	stor = new_storage(sctx_ STOR_VALUE);

	if (stor) {
		stor->flags |= STOR_WANTS_FREE;
		stor->value = value;
	}

	return stor;
}

static int new_label(SCTX)
{
	static int label = 0;
	return ++label;
}

static void textbuf_push(SCTX_ struct textbuf **buf_p, const char *text)
{
	struct textbuf *tmp, *list = *buf_p;
	unsigned int text_len = strlen(text);
	unsigned int alloc_len = text_len + 1 + sizeof(*list);

	tmp = calloc(1, alloc_len);
	if (!tmp)
		sparse_die(sctx_ "OOM on textbuf alloc");

	tmp->text = ((void *) tmp) + sizeof(*tmp);
	memcpy(tmp->text, text, text_len + 1);
	tmp->len = text_len;

	/* add to end of list */
	if (!list) {
		list = tmp;
		tmp->prev = tmp;
	} else {
		tmp->prev = list->prev;
		tmp->prev->next = tmp;
		list->prev = tmp;
	}
	tmp->next = list;

	*buf_p = list;
}

static void textbuf_emit(SCTX_ struct textbuf **buf_p)
{
	struct textbuf *tmp, *list = *buf_p;

	while (list) {
		tmp = list;
		if (tmp->next == tmp)
			list = NULL;
		else {
			tmp->prev->next = tmp->next;
			tmp->next->prev = tmp->prev;
			list = tmp->next;
		}

		fputs(tmp->text, stdout);

		free(tmp);
	}

	*buf_p = list;
}

static void insn(SCTX_ const char *insn, struct storage *op1, struct storage *op2,
		 const char *comment_in)
{
	struct function *f = current_func;
	struct atom *atom = new_atom(sctx_ ATOM_INSN);

	assert(insn != NULL);

	strcpy(atom->insn, insn);
	if (comment_in && (*comment_in))
		strncpy(atom->comment, comment_in,
			sizeof(atom->comment) - 1);

	atom->op1 = op1;
	atom->op2 = op2;

	push_atom(sctx_ f, atom);
}

static void emit_comment(SCTX_ const char *fmt, ...)
{
	struct function *f = current_func;
	static char tmpbuf[100] = "\t# ";
	va_list args;
	int i;

	va_start(args, fmt);
	i = vsnprintf(tmpbuf+3, sizeof(tmpbuf)-4, fmt, args);
	va_end(args);
	tmpbuf[i+3] = '\n';
	tmpbuf[i+4] = '\0';
	push_text_atom(sctx_ f, tmpbuf);
}

static void emit_label (SCTX_ int label, const char *comment)
{
	struct function *f = current_func;
	char s[64];

	if (!comment)
		sprintf(s, ".L%d:\n", label);
	else
		sprintf(s, ".L%d:\t\t\t\t\t# %s\n", label, comment);

	push_text_atom(sctx_ f, s);
}

static void emit_labelsym (SCTX_ struct symbol *sym, const char *comment)
{
	struct function *f = current_func;
	char s[64];

	if (!comment)
		sprintf(s, ".LS%p:\n", sym);
	else
		sprintf(s, ".LS%p:\t\t\t\t# %s\n", sym, comment);

	push_text_atom(sctx_ f, s);
}

void emit_unit_begin(SCTX_ const char *basename)
{
	printf("\t.file\t\"%s\"\n", basename);
}

void emit_unit_end(SCTX)
{
	textbuf_emit(sctx_ &unit_post_text);
	printf("\t.ident\t\"sparse silly x86 backend (built %s)\"\n", __DATE__);
}

/* conditionally switch sections */
static void emit_section(SCTX_ const char *s)
{
	if (s == current_section)
		return;
	if (current_section && (!strcmp(s, current_section)))
		return;

	printf("\t%s\n", s);
	current_section = s;
}

static void emit_insn_atom(SCTX_ struct function *f, struct atom *atom)
{
	char s[128];
	char comment[64];
	struct storage *op1 = atom->op1;
	struct storage *op2 = atom->op2;

	if (atom->comment[0])
		sprintf(comment, "\t\t# %s", atom->comment);
	else
		comment[0] = 0;

	if (atom->op2) {
		char tmp[16];
		strcpy(tmp, stor_op_name(sctx_ op1));
		sprintf(s, "\t%s\t%s, %s%s\n",
			atom->insn, tmp, stor_op_name(sctx_ op2), comment);
	} else if (atom->op1)
		sprintf(s, "\t%s\t%s%s%s\n",
			atom->insn, stor_op_name(sctx_ op1),
			comment[0] ? "\t" : "", comment);
	else
		sprintf(s, "\t%s\t%s%s\n",
			atom->insn,
			comment[0] ? "\t\t" : "", comment);

	write(STDOUT_FILENO, s, strlen(s));
}

static void emit_atom_list(SCTX_ struct function *f)
{
	struct atom *atom;

	FOR_EACH_PTR(f->atom_list, atom) {
		switch (atom->type) {
		case ATOM_TEXT: {
			ssize_t rc = write(STDOUT_FILENO, atom->text,
					   atom->text_len);
			(void) rc;	/* FIXME */
			break;
		}
		case ATOM_INSN:
			emit_insn_atom(sctx_ f, atom);
			break;
		case ATOM_CSTR:
			assert(0);
			break;
		}
	} END_FOR_EACH_PTR(atom);
}

static void emit_string_list(SCTX_ struct function *f)
{
	struct atom *atom;

	emit_section(sctx_ ".section\t.rodata");

	FOR_EACH_PTR(f->str_list, atom) {
		/* FIXME: escape " in string */
		printf(".L%d:\n", atom->label);
		printf("\t.string\t%s\n", show_string(sctx_ atom->string));

		free(atom);
	} END_FOR_EACH_PTR(atom);
}

static void func_cleanup(SCTX_ struct function *f)
{
	struct storage *stor;
	struct atom *atom;

	FOR_EACH_PTR(f->atom_list, atom) {
		if ((atom->type == ATOM_TEXT) && (atom->text))
			free(atom->text);
		if (atom->op1 && (atom->op1->flags & STOR_WANTS_FREE))
			free(atom->op1);
		if (atom->op2 && (atom->op2->flags & STOR_WANTS_FREE))
			free(atom->op2);
		free(atom);
	} END_FOR_EACH_PTR(atom);

	FOR_EACH_PTR(f->pseudo_list, stor) {
		free(stor);
	} END_FOR_EACH_PTR(stor);

	free_ptr_list(&f->pseudo_list);
	free(f);
}

/* function prologue */
static void emit_func_pre(SCTX_ struct symbol *sym)
{
	struct function *f;
	struct symbol *arg;
	unsigned int i, argc = 0, alloc_len;
	unsigned char *mem;
	struct symbol_private *privbase;
	struct storage *storage_base;
	struct symbol *base_type = sym->ctype.base_type;

	FOR_EACH_PTR(base_type->arguments, arg) {
		argc++;
	} END_FOR_EACH_PTR(arg);

	alloc_len =
		sizeof(*f) +
		(argc * sizeof(struct symbol *)) +
		(argc * sizeof(struct symbol_private)) +
		(argc * sizeof(struct storage));
	mem = calloc(1, alloc_len);
	if (!mem)
		sparse_die(sctx_ "OOM on func info");

	f		=  (struct function *) mem;
	mem		+= sizeof(*f);
	f->argv		=  (struct symbol **) mem;
	mem		+= (argc * sizeof(struct symbol *));
	privbase	=  (struct symbol_private *) mem;
	mem		+= (argc * sizeof(struct symbol_private));
	storage_base	=  (struct storage *) mem;

	f->argc = argc;
	f->ret_target = new_label(sctx);

	i = 0;
	FOR_EACH_PTR(base_type->arguments, arg) {
		f->argv[i] = arg;
		arg->aux = &privbase[i];
		storage_base[i].type = STOR_ARG;
		storage_base[i].idx = i;
		privbase[i].addr = &storage_base[i];
		i++;
	} END_FOR_EACH_PTR(arg);

	assert(current_func == NULL);
	current_func = f;
}

/* function epilogue */
static void emit_func_post(SCTX_ struct symbol *sym)
{
	const char *name = show_ident(sctx_ sym->ident);
	struct function *f = current_func;
	int stack_size = f->stack_size;

	if (f->str_list)
		emit_string_list(sctx_ f);

	/* function prologue */
	emit_section(sctx_ ".text");
	if ((sym->ctype.modifiers & MOD_STATIC) == 0)
		printf(".globl %s\n", name);
	printf("\t.type\t%s, @function\n", name);
	printf("%s:\n", name);

	if (stack_size) {
		char pseudo_const[16];

		sprintf(pseudo_const, "$%d", stack_size);
		printf("\tsubl\t%s, %%esp\n", pseudo_const);
	}

	/* function epilogue */

	/* jump target for 'return' statements */
	emit_label(sctx_ f->ret_target, NULL);

	if (stack_size) {
		struct storage *val;

		val = new_storage(sctx_ STOR_VALUE);
		val->value = (long long) (stack_size);
		val->flags = STOR_WANTS_FREE;

		insn(sctx_ "addl", val, REG_ESP, NULL);
	}

	insn(sctx_ "ret", NULL, NULL, NULL);

	/* output everything to stdout */
	fflush(stdout);		/* paranoia; needed? */
	emit_atom_list(sctx_ f);

	/* function footer */
	name = show_ident(sctx_ sym->ident);
	printf("\t.size\t%s, .-%s\n", name, name);

	func_cleanup(sctx_ f);
	current_func = NULL;
}

/* emit object (a.k.a. variable, a.k.a. data) prologue */
static void emit_object_pre(SCTX_ const char *name, unsigned long modifiers,
			    unsigned long alignment, unsigned int byte_size)
{
	if ((modifiers & MOD_STATIC) == 0)
		printf(".globl %s\n", name);
	emit_section(sctx_ ".data");
	if (alignment)
		printf("\t.align %lu\n", alignment);
	printf("\t.type\t%s, @object\n", name);
	printf("\t.size\t%s, %d\n", name, byte_size);
	printf("%s:\n", name);
}

/* emit value (only) for an initializer scalar */
static void emit_scalar(SCTX_ struct expression *expr, unsigned int bit_size)
{
	const char *type;
	long long ll;

	assert(expr->type == EXPR_VALUE);

	if (expr->value == 0ULL) {
		printf("\t.zero\t%d\n", bit_size / 8);
		return;
	}

	ll = (long long) expr->value;

	switch (bit_size) {
	case 8:		type = "byte";	ll = (char) ll; break;
	case 16:	type = "value";	ll = (short) ll; break;
	case 32:	type = "long";	ll = (int) ll; break;
	case 64:	type = "quad";	break;
	default:	type = NULL;	break;
	}

	assert(type != NULL);

	printf("\t.%s\t%Ld\n", type, ll);
}

static void emit_global_noinit(SCTX_ const char *name, unsigned long modifiers,
			       unsigned long alignment, unsigned int byte_size)
{
	char s[64];

	if (modifiers & MOD_STATIC) {
		sprintf(s, "\t.local\t%s\n", name);
		textbuf_push(sctx_ &unit_post_text, s);
	}
	if (alignment)
		sprintf(s, "\t.comm\t%s,%d,%lu\n", name, byte_size, alignment);
	else
		sprintf(s, "\t.comm\t%s,%d\n", name, byte_size);
	textbuf_push(sctx_ &unit_post_text, s);
}

static int ea_current, ea_last;

static void emit_initializer(SCTX_ struct symbol *sym,
			     struct expression *expr)
{
	int distance = ea_current - ea_last - 1;

	if (distance > 0)
		printf("\t.zero\t%d\n", (sym->bit_size / 8) * distance);

	if (expr->type == EXPR_VALUE) {
		struct symbol *base_type = sym->ctype.base_type;
		assert(base_type != NULL);

		emit_scalar(sctx_ expr, sym->bit_size / get_expression_value(sctx_ base_type->array_size));
		return;
	}
	if (expr->type != EXPR_INITIALIZER)
		return;

	assert(0); /* FIXME */
}

static int sort_array_cmp(SCTX_ const struct expression *a,
			  const struct expression *b)
{
	int a_ofs = 0, b_ofs = 0;

	if (a->type == EXPR_POS)
		a_ofs = (int) a->init_offset;
	if (b->type == EXPR_POS)
		b_ofs = (int) b->init_offset;

	return a_ofs - b_ofs;
}

/* move to front-end? */
static void sort_array(SCTX_ struct expression *expr)
{
	struct expression *entry, **list;
	unsigned int elem, sorted, i;

	elem = 0;
	FOR_EACH_PTR(expr->expr_list, entry) {
		elem++;
	} END_FOR_EACH_PTR(entry);

	if (!elem)
		return;

	list = malloc(sizeof(entry) * elem);
	if (!list)
		sparse_die(sctx_ "OOM in sort_array");

	/* this code is no doubt evil and ignores EXPR_INDEX possibly
	 * to its detriment and other nasty things.  improvements
	 * welcome.
	 */
	i = 0;
	sorted = 0;
	FOR_EACH_PTR(expr->expr_list, entry) {
		if ((entry->type == EXPR_POS) || (entry->type == EXPR_VALUE)) {
			/* add entry to list[], in sorted order */
			if (sorted == 0) {
				list[0] = entry;
				sorted = 1;
			} else {
				for (i = 0; i < sorted; i++)
					if (sort_array_cmp(sctx_ entry, list[i]) <= 0)
						break;

				/* If inserting into the middle of list[]
				 * instead of appending, we memmove.
				 * This is ugly, but thankfully
				 * uncommon.  Input data with tons of
				 * entries very rarely have explicit
				 * offsets.  convert to qsort eventually...
				 */
				if (i != sorted)
					memmove(&list[i + 1], &list[i],
						(sorted - i) * sizeof(entry));
				list[i] = entry;
				sorted++;
			}
		}
	} END_FOR_EACH_PTR(entry);

	i = 0;
	FOR_EACH_PTR(expr->expr_list, entry) {
		if ((entry->type == EXPR_POS) || (entry->type == EXPR_VALUE))
			*THIS_ADDRESS(entry) = list[i++];
	} END_FOR_EACH_PTR(entry);

}

static void emit_array(SCTX_ struct symbol *sym)
{
	struct symbol *base_type = sym->ctype.base_type;
	struct expression *expr = sym->initializer;
	struct expression *entry;

	assert(base_type != NULL);

	stor_sym_init(sctx_ sym);

	ea_last = -1;

	emit_object_pre(sctx_ show_ident(sctx_ sym->ident), sym->ctype.modifiers,
		        sym->ctype.alignment,
			sym->bit_size / 8);

	sort_array(sctx_ expr);

	FOR_EACH_PTR(expr->expr_list, entry) {
		if (entry->type == EXPR_VALUE) {
			ea_current = 0;
			emit_initializer(sctx_ sym, entry);
			ea_last = ea_current;
		} else if (entry->type == EXPR_POS) {
			ea_current =
			    entry->init_offset / (base_type->bit_size / 8);
			emit_initializer(sctx_ sym, entry->init_expr);
			ea_last = ea_current;
		}
	} END_FOR_EACH_PTR(entry);
}

void emit_one_symbol(SCTX_ struct symbol *sym)
{
	x86_symbol(sctx_ sym);
}

static void emit_copy(SCTX_ struct storage *dest, struct storage *src,
		      struct symbol *ctype)
{
	struct storage *reg = NULL;
	unsigned int bit_size;

	/* FIXME: Bitfield copy! */

	bit_size = src->size * 8;
	if (!bit_size)
		bit_size = 32;
	if ((src->type == STOR_ARG) && (bit_size < 32))
		bit_size = 32;

	reg = temp_from_bits(sctx_ bit_size);
	emit_move(sctx_ src, reg, ctype, "begin copy ..");

	bit_size = dest->size * 8;
	if (!bit_size)
		bit_size = 32;
	if ((dest->type == STOR_ARG) && (bit_size < 32))
		bit_size = 32;

	emit_move(sctx_ reg, dest, ctype, ".... end copy");
	put_reg(sctx_ reg);
}

static void emit_store(SCTX_ struct expression *dest_expr, struct storage *dest,
		       struct storage *src, int bits)
{
	/* FIXME: Bitfield store! */
	printf("\tst.%d\t\tv%d,[v%d]\n", bits, src->pseudo, dest->pseudo);
}

static void emit_scalar_noinit(SCTX_ struct symbol *sym)
{
	emit_global_noinit(sctx_ show_ident(sctx_ sym->ident),
			   sym->ctype.modifiers, sym->ctype.alignment,
			   sym->bit_size / 8);
	stor_sym_init(sctx_ sym);
}

static void emit_array_noinit(SCTX_ struct symbol *sym)
{
	emit_global_noinit(sctx_ show_ident(sctx_ sym->ident),
			   sym->ctype.modifiers, sym->ctype.alignment,
			   get_expression_value(sctx_ sym->array_size) * (sym->bit_size / 8));
	stor_sym_init(sctx_ sym);
}

static const char *opbits(SCTX_ const char *insn, unsigned int bits)
{
	static char opbits_str[32];
	char c;

	switch (bits) {
	case 8:	 c = 'b'; break;
	case 16: c = 'w'; break;
	case 32: c = 'l'; break;
	case 64: c = 'q'; break;
	default: abort(); break;
	}

	sprintf(opbits_str, "%s%c", insn, c);

	return opbits_str;
}

static void emit_move(SCTX_ struct storage *src, struct storage *dest,
		      struct symbol *ctype, const char *comment)
{
	unsigned int bits;
	unsigned int is_signed;
	unsigned int is_dest = (src->type == STOR_REG);
	const char *opname;

	if (ctype) {
		bits = ctype->bit_size;
		is_signed = type_is_signed(sctx_ ctype);
	} else {
		bits = 32;
		is_signed = 0;
	}

	/*
	 * Are we moving from a register to a register?
	 * Make the new reg to be the "cache".
	 */
	if ((dest->type == STOR_REG) && (src->type == STOR_REG)) {
		struct storage *backing;

reg_reg_move:
		if (dest == src)
			return;

		backing = src->reg->contains;
		if (backing) {
			/* Is it still valid? */
			if (backing->reg != src->reg)
				backing = NULL;
			else
				backing->reg = dest->reg;
		}
		dest->reg->contains = backing;
		insn(sctx_ "mov", src, dest, NULL);
		return;
	}

	/*
	 * Are we moving to a register from a non-reg?
	 *
	 * See if we have the non-reg source already cached
	 * in a register..
	 */
	if (dest->type == STOR_REG) {
		if (src->reg) {
			struct reg_info *info = src->reg;
			if (info->contains == src) {
				src = reginfo_reg(info);
				goto reg_reg_move;
			}
		}
		dest->reg->contains = src;
		src->reg = dest->reg;
	}

	if (src->type == STOR_REG) {
		/* We could just mark the register dirty here and do lazy store.. */
		src->reg->contains = dest;
		dest->reg = src->reg;
	}

	if ((bits == 8) || (bits == 16)) {
		if (is_dest)
			opname = "mov";
		else
			opname = is_signed ? "movsx" : "movzx";
	} else
		opname = "mov";

	insn(sctx_ opbits(sctx_ opname, bits), src, dest, comment);
}

static struct storage *emit_compare(SCTX_ struct expression *expr)
{
	struct storage *left = x86_expression(sctx_ expr->left);
	struct storage *right = x86_expression(sctx_ expr->right);
	struct storage *reg1, *reg2;
	struct storage *new, *val;
	const char *opname = NULL;
	unsigned int right_bits = expr->right->ctype->bit_size;

	switch(expr->op) {
	case '<': 		opname = "setl";	break;
	case '>':		opname = "setg";	break;
	case SPECIAL_LTE:
				opname = "setle";	break;
	case SPECIAL_GTE:
				opname = "setge";	break;
	case SPECIAL_EQUAL:	opname = "sete";	break;
	case SPECIAL_NOTEQUAL:	opname = "setne";	break;
	case SPECIAL_UNSIGNED_LT:
				opname = "setb";	break;
	case SPECIAL_UNSIGNED_GT:
				opname = "seta";	break;
	case SPECIAL_UNSIGNED_LTE:
				opname = "setb";	break;
	case SPECIAL_UNSIGNED_GTE:
				opname = "setae";	break;
	default:
		assert(0);
		break;
	}

	/* init EDX to 0 */
	val = new_storage(sctx_ STOR_VALUE);
	val->flags = STOR_WANTS_FREE;

	reg1 = get_reg(sctx_ &regclass_32_8);
	emit_move(sctx_ val, reg1, NULL, NULL);

	/* move op1 into EAX */
	reg2 = get_reg_value(sctx_ left, get_regclass(sctx_ expr->left));

	/* perform comparison, RHS (op1, right) and LHS (op2, EAX) */
	insn(sctx_ opbits(sctx_ "cmp", right_bits), right, reg2, NULL);
	put_reg(sctx_ reg2);

	/* store result of operation, 0 or 1, in DL using SETcc */
	insn(sctx_ opname, byte_reg(reg1), NULL, NULL);

	/* finally, store the result (DL) in a new pseudo / stack slot */
	new = stack_alloc(sctx_ 4);
	emit_move(sctx_ reg1, new, NULL, "end EXPR_COMPARE");
	put_reg(sctx_ reg1);

	return new;
}

static struct storage *emit_value(SCTX_ struct expression *expr)
{
#if 0 /* old and slow way */
	struct storage *new = stack_alloc(4);
	struct storage *val;

	val = new_storage(sctx_ STOR_VALUE);
	val->value = (long long) expr->value;
	val->flags = STOR_WANTS_FREE;
	insn(sctx_ "movl", val, new, NULL);

	return new;
#else
	struct storage *val;

	val = new_storage(sctx_ STOR_VALUE);
	val->value = (long long) expr->value;

	return val;	/* FIXME: memory leak */
#endif
}

static struct storage *emit_divide(SCTX_ struct expression *expr, struct storage *left, struct storage *right)
{
	struct storage *eax_edx;
	struct storage *reg, *new;
	struct storage *val = new_storage(sctx_ STOR_VALUE);

	emit_comment(sctx_ "begin DIVIDE");
	eax_edx = get_hardreg(sctx_ hardreg_storage_table + EAX_EDX, 1);

	/* init EDX to 0 */
	val->flags = STOR_WANTS_FREE;
	emit_move(sctx_ val, REG_EDX, NULL, NULL);

	new = stack_alloc(sctx_ expr->ctype->bit_size / 8);

	/* EAX is dividend */
	emit_move(sctx_ left, REG_EAX, NULL, NULL);

	reg = get_reg_value(sctx_ right, &regclass_32);

	/* perform binop */
	insn(sctx_ "div", reg, REG_EAX, NULL);
	put_reg(sctx_ reg);

	reg = REG_EAX;
	if (expr->op == '%')
		reg = REG_EDX;
	emit_move(sctx_ reg, new, NULL, NULL);

	put_reg(sctx_ eax_edx);
	emit_comment(sctx_ "end DIVIDE");
	return new;
}

static struct storage *emit_binop(SCTX_ struct expression *expr)
{
	struct storage *left = x86_expression(sctx_ expr->left);
	struct storage *right = x86_expression(sctx_ expr->right);
	struct storage *new;
	struct storage *dest, *src;
	const char *opname = NULL;
	const char *suffix = NULL;
	char opstr[16];
	int is_signed;

	/* Divides have special register constraints */
	if ((expr->op == '/') || (expr->op == '%'))
		return emit_divide(sctx_ expr, left, right);

	is_signed = type_is_signed(sctx_ expr->ctype);

	switch (expr->op) {
	case '+':
		opname = "add";
		break;
	case '-':
		opname = "sub";
		break;
	case '&':
		opname = "and";
		break;
	case '|':
		opname = "or";
		break;
	case '^':
		opname = "xor";
		break;
	case SPECIAL_LEFTSHIFT:
		opname = "shl";
		break;
	case SPECIAL_RIGHTSHIFT:
		if (is_signed)
			opname = "sar";
		else
			opname = "shr";
		break;
	case '*':
		if (is_signed)
			opname = "imul";
		else
			opname = "mul";
		break;
	case SPECIAL_LOGICAL_AND:
		warning(sctx_ expr->pos->pos, "bogus bitwise and for logical op (should use '2*setne + and' or something)");
		opname = "and";
		break;
	case SPECIAL_LOGICAL_OR:
		warning(sctx_ expr->pos->pos, "bogus bitwise or for logical op (should use 'or + setne' or something)");
		opname = "or";
		break;
	default:
		error_die(sctx_ expr->pos->pos, "unhandled binop '%s'\n", show_special(sctx_ expr->op));
		break;
	}

	dest = get_reg_value(sctx_ right, &regclass_32);
	src = get_reg_value(sctx_ left, &regclass_32);
	switch (expr->ctype->bit_size) {
	case 8:
		suffix = "b";
		break;
	case 16:
		suffix = "w";
		break;
	case 32:
		suffix = "l";
		break;
	case 64:
		suffix = "q";		/* FIXME */
		break;
	default:
		assert(0);
		break;
	}

	snprintf(opstr, sizeof(opstr), "%s%s", opname, suffix);

	/* perform binop */
	insn(sctx_ opstr, src, dest, NULL);
	put_reg(sctx_ src);

	/* store result in new pseudo / stack slot */
	new = stack_alloc(sctx_ expr->ctype->bit_size / 8);
	emit_move(sctx_ dest, new, NULL, "end EXPR_BINOP");

	put_reg(sctx_ dest);

	return new;
}

static int emit_conditional_test(SCTX_ struct storage *val)
{
	struct storage *reg;
	struct storage *target_val;
	int target_false;

	/* load result into EAX */
	emit_comment(sctx_ "begin if/conditional");
	reg = get_reg_value(sctx_ val, &regclass_32);

	/* compare result with zero */
	insn(sctx_ "test", reg, reg, NULL);
	put_reg(sctx_ reg);

	/* create conditional-failed label to jump to */
	target_false = new_label(sctx);
	target_val = new_storage(sctx_ STOR_LABEL);
	target_val->label = target_false;
	target_val->flags = STOR_WANTS_FREE;
	insn(sctx_ "jz", target_val, NULL, NULL);

	return target_false;
}

static int emit_conditional_end(SCTX_ int target_false)
{
	struct storage *cond_end_st;
	int cond_end;

	/* finished generating code for if-true statement.
	 * add a jump-to-end jump to avoid falling through
	 * to the if-false statement code.
	 */
	cond_end = new_label(sctx);
	cond_end_st = new_storage(sctx_ STOR_LABEL);
	cond_end_st->label = cond_end;
	cond_end_st->flags = STOR_WANTS_FREE;
	insn(sctx_ "jmp", cond_end_st, NULL, NULL);

	/* if we have both if-true and if-false statements,
	 * the failed-conditional case will fall through to here
	 */
	emit_label(sctx_ target_false, NULL);

	return cond_end;
}

static void emit_if_conditional(SCTX_ struct statement *stmt)
{
	struct storage *val;
	int cond_end;

	/* emit test portion of conditional */
	val = x86_expression(sctx_ stmt->if_conditional);
	cond_end = emit_conditional_test(sctx_ val);

	/* emit if-true statement */
	x86_statement(sctx_ stmt->if_true);

	/* emit if-false statement, if present */
	if (stmt->if_false) {
		cond_end = emit_conditional_end(sctx_ cond_end);
		x86_statement(sctx_ stmt->if_false);
	}

	/* end of conditional; jump target for if-true branch */
	emit_label(sctx_ cond_end, "end if");
}

static struct storage *emit_inc_dec(SCTX_ struct expression *expr, int postop)
{
	struct storage *addr = x86_address_gen(sctx_ expr->unop);
	struct storage *retval;
	char opname[16];

	strcpy(opname, opbits(sctx_ expr->op == SPECIAL_INCREMENT ? "inc" : "dec",
			      expr->ctype->bit_size));

	if (postop) {
		struct storage *new = stack_alloc(sctx_ 4);

		emit_copy(sctx_ new, addr, expr->unop->ctype);

		retval = new;
	} else
		retval = addr;

	insn(sctx_ opname, addr, NULL, NULL);

	return retval;
}

static struct storage *emit_postop(SCTX_ struct expression *expr)
{
	return emit_inc_dec(sctx_ expr, 1);
}

static struct storage *emit_return_stmt(SCTX_ struct statement *stmt)
{
	struct function *f = current_func;
	struct expression *expr = stmt->ret_value;
	struct storage *val = NULL, *jmplbl;

	if (expr && expr->ctype) {
		val = x86_expression(sctx_ expr);
		assert(val != NULL);
		emit_move(sctx_ val, REG_EAX, expr->ctype, "return");
	}

	jmplbl = new_storage(sctx_ STOR_LABEL);
	jmplbl->flags |= STOR_WANTS_FREE;
	jmplbl->label = f->ret_target;
	insn(sctx_ "jmp", jmplbl, NULL, NULL);

	return val;
}

static struct storage *emit_conditional_expr(SCTX_ struct expression *expr)
{
	struct storage *cond, *true = NULL, *false = NULL;
	struct storage *new = stack_alloc(sctx_ expr->ctype->bit_size / 8);
	int target_false, cond_end;

	/* evaluate conditional */
	cond = x86_expression(sctx_ expr->conditional);
	target_false = emit_conditional_test(sctx_ cond);

	/* handle if-true part of the expression */
	true = x86_expression(sctx_ expr->cond_true);

	emit_copy(sctx_ new, true, expr->ctype);

	cond_end = emit_conditional_end(sctx_ target_false);

	/* handle if-false part of the expression */
	false = x86_expression(sctx_ expr->cond_false);

	emit_copy(sctx_ new, false, expr->ctype);

	/* end of conditional; jump target for if-true branch */
	emit_label(sctx_ cond_end, "end conditional");

	return new;
}

static struct storage *emit_select_expr(SCTX_ struct expression *expr)
{
	struct storage *cond = x86_expression(sctx_ expr->conditional);
	struct storage *true = x86_expression(sctx_ expr->cond_true);
	struct storage *false = x86_expression(sctx_ expr->cond_false);
	struct storage *reg_cond, *reg_true, *reg_false;
	struct storage *new = stack_alloc(sctx_ 4);

	emit_comment(sctx_ "begin SELECT");
	reg_cond = get_reg_value(sctx_ cond, get_regclass(sctx_ expr->conditional));
	reg_true = get_reg_value(sctx_ true, get_regclass(sctx_ expr));
	reg_false = get_reg_value(sctx_ false, get_regclass(sctx_ expr));

	/*
	 * Do the actual select: check the conditional for zero,
	 * move false over true if zero
	 */ 
	insn(sctx_ "test", reg_cond, reg_cond, NULL);
	insn(sctx_ "cmovz", reg_false, reg_true, NULL);

	/* Store it back */
	emit_move(sctx_ reg_true, new, expr->ctype, NULL);
	put_reg(sctx_ reg_cond);
	put_reg(sctx_ reg_true);
	put_reg(sctx_ reg_false);
	emit_comment(sctx_ "end SELECT");
	return new;
}

static struct storage *emit_symbol_expr_init(SCTX_ struct symbol *sym)
{
	struct expression *expr = sym->initializer;
	struct symbol_private *priv = sym->aux;

	if (priv == NULL) {
		priv = calloc(1, sizeof(*priv));
		sym->aux = priv;

		if (expr == NULL) {
			struct storage *new = stack_alloc(sctx_ 4);
			fprintf(stderr, "FIXME! no value for symbol %s.  creating pseudo %d (stack offset %d)\n",
				show_ident(sctx_ sym->ident),
				new->pseudo, new->pseudo * 4);
			priv->addr = new;
		} else {
			priv->addr = x86_expression(sctx_ expr);
		}
	}

	return priv->addr;
}

static struct storage *emit_string_expr(SCTX_ struct expression *expr)
{
	struct function *f = current_func;
	int label = new_label(sctx);
	struct storage *new;

	push_cstring(sctx_ f, expr->string, label);

	new = new_storage(sctx_ STOR_LABEL);
	new->label = label;
	new->flags = STOR_LABEL_VAL | STOR_WANTS_FREE;
	return new;
}

static struct storage *emit_cast_expr(SCTX_ struct expression *expr)
{
	struct symbol *old_type, *new_type;
	struct storage *op = x86_expression(sctx_ expr->cast_expression);
	int oldbits, newbits;
	struct storage *new;

	old_type = expr->cast_expression->ctype;
	new_type = expr->cast_type;

	oldbits = old_type->bit_size;
	newbits = new_type->bit_size;
	if (oldbits >= newbits)
		return op;

	emit_move(sctx_ op, REG_EAX, old_type, "begin cast ..");

	new = stack_alloc(sctx_ newbits / 8);
	emit_move(sctx_ REG_EAX, new, new_type, ".... end cast");

	return new;
}

static struct storage *emit_regular_preop(SCTX_ struct expression *expr)
{
	struct storage *target = x86_expression(sctx_ expr->unop);
	struct storage *val, *new = stack_alloc(sctx_ 4);
	const char *opname = NULL;

	switch (expr->op) {
	case '!':
		val = new_storage(sctx_ STOR_VALUE);
		val->flags = STOR_WANTS_FREE;
		emit_move(sctx_ val, REG_EDX, NULL, NULL);
		emit_move(sctx_ target, REG_EAX, expr->unop->ctype, NULL);
		insn(sctx_ "test", REG_EAX, REG_EAX, NULL);
		insn(sctx_ "setz", REG_DL, NULL, NULL);
		emit_move(sctx_ REG_EDX, new, expr->unop->ctype, NULL);

		break;
	case '~':
		opname = "not";
	case '-':
		if (!opname)
			opname = "neg";
		emit_move(sctx_ target, REG_EAX, expr->unop->ctype, NULL);
		insn(sctx_ opname, REG_EAX, NULL, NULL);
		emit_move(sctx_ REG_EAX, new, expr->unop->ctype, NULL);
		break;
	default:
		assert(0);
		break;
	}

	return new;
}

static void emit_case_statement(SCTX_ struct statement *stmt)
{
	emit_labelsym(sctx_ stmt->case_label, NULL);
	x86_statement(sctx_ stmt->case_statement);
}

static void emit_switch_statement(SCTX_ struct statement *stmt)
{
	struct storage *val = x86_expression(sctx_ stmt->switch_expression);
	struct symbol *sym, *default_sym = NULL;
	struct storage *labelsym, *label;
	int switch_end = 0;

	emit_move(sctx_ val, REG_EAX, stmt->switch_expression->ctype, "begin case");

	/*
	 * This is where a _real_ back-end would go through the
	 * cases to decide whether to use a lookup table or a
	 * series of comparisons etc
	 */
	FOR_EACH_PTR(stmt->switch_case->symbol_list, sym) {
		struct statement *case_stmt = sym->stmt;
		struct expression *expr = case_stmt->case_expression;
		struct expression *to = case_stmt->case_to;

		/* default: */
		if (!expr)
			default_sym = sym;

		/* case NNN: */
		else {
			struct storage *case_val = new_val(sctx_ expr->value);

			assert (expr->type == EXPR_VALUE);

			insn(sctx_ "cmpl", case_val, REG_EAX, NULL);

			if (!to) {
				labelsym = new_labelsym(sctx_ sym);
				insn(sctx_ "je", labelsym, NULL, NULL);
			} else {
				int next_test;

				label = new_storage(sctx_ STOR_LABEL);
				label->flags |= STOR_WANTS_FREE;
				label->label = next_test = new_label(sctx);

				/* FIXME: signed/unsigned */
				insn(sctx_ "jl", label, NULL, NULL);

				case_val = new_val(sctx_ to->value);
				insn(sctx_ "cmpl", case_val, REG_EAX, NULL);

				/* TODO: implement and use refcounting... */
				label = new_storage(sctx_ STOR_LABEL);
				label->flags |= STOR_WANTS_FREE;
				label->label = next_test;

				/* FIXME: signed/unsigned */
				insn(sctx_ "jg", label, NULL, NULL);

				labelsym = new_labelsym(sctx_ sym);
				insn(sctx_ "jmp", labelsym, NULL, NULL);

				emit_label(sctx_ next_test, NULL);
			}
		}
	} END_FOR_EACH_PTR(sym);

	if (default_sym) {
		labelsym = new_labelsym(sctx_ default_sym);
		insn(sctx_ "jmp", labelsym, NULL, "default");
	} else {
		label = new_storage(sctx_ STOR_LABEL);
		label->flags |= STOR_WANTS_FREE;
		label->label = switch_end = new_label(sctx);
		insn(sctx_ "jmp", label, NULL, "goto end of switch");
	}

	x86_statement(sctx_ stmt->switch_statement);

	if (stmt->switch_break->used)
		emit_labelsym(sctx_ stmt->switch_break, NULL);

	if (switch_end)
		emit_label(sctx_ switch_end, NULL);
}

static void x86_struct_member(SCTX_ struct symbol *sym)
{
	printf("\t%s:%d:%ld at offset %ld.%d", show_ident(sctx_ sym->ident), sym->bit_size, sym->ctype.alignment, sym->offset, sym->bit_offset);
	printf("\n");
}

static void x86_symbol(SCTX_ struct symbol *sym)
{
	struct symbol *type;

	if (!sym)
		return;

	type = sym->ctype.base_type;
	if (!type)
		return;

	/*
	 * Show actual implementation information
	 */
	switch (type->type) {

	case SYM_ARRAY:
		if (sym->initializer)
			emit_array(sctx_ sym);
		else
			emit_array_noinit(sctx_ sym);
		break;

	case SYM_BASETYPE:
		if (sym->initializer) {
			emit_object_pre(sctx_ show_ident(sctx_ sym->ident),
					sym->ctype.modifiers,
				        sym->ctype.alignment,
					sym->bit_size / 8);
			emit_scalar(sctx_ sym->initializer, sym->bit_size);
			stor_sym_init(sctx_ sym);
		} else
			emit_scalar_noinit(sctx_ sym);
		break;

	case SYM_STRUCT:
	case SYM_UNION: {
		struct symbol *member;

		printf(" {\n");
		FOR_EACH_PTR(type->symbol_list, member) {
			x86_struct_member(sctx_ member);
		} END_FOR_EACH_PTR(member);
		printf("}\n");
		break;
	}

	case SYM_FN: {
		struct statement *stmt = type->stmt;
		if (stmt) {
			emit_func_pre(sctx_ sym);
			x86_statement(sctx_ stmt);
			emit_func_post(sctx_ sym);
		}
		break;
	}

	default:
		break;
	}

	if (sym->initializer && (type->type != SYM_BASETYPE) &&
	    (type->type != SYM_ARRAY)) {
		printf(" = \n");
		x86_expression(sctx_ sym->initializer);
	}
}

static void x86_symbol_init(SCTX_ struct symbol *sym);

static void x86_symbol_decl(SCTX_ struct symbol_list *syms)
{
	struct symbol *sym;
	FOR_EACH_PTR(syms, sym) {
		x86_symbol_init(sctx_ sym);
	} END_FOR_EACH_PTR(sym);
}

static void loopstk_push(SCTX_ int cont_lbl, int loop_bottom_lbl)
{
	struct function *f = current_func;
	struct loop_stack *ls;

	ls = malloc(sizeof(*ls));
	ls->continue_lbl = cont_lbl;
	ls->loop_bottom_lbl = loop_bottom_lbl;
	ls->next = f->loop_stack;
	f->loop_stack = ls;
}

static void loopstk_pop(SCTX)
{
	struct function *f = current_func;
	struct loop_stack *ls;

	assert(f->loop_stack != NULL);
	ls = f->loop_stack;
	f->loop_stack = f->loop_stack->next;
	free(ls);
}

static int loopstk_break(SCTX)
{
	return current_func->loop_stack->loop_bottom_lbl;
}

static int loopstk_continue(SCTX)
{
	return current_func->loop_stack->continue_lbl;
}

static void emit_loop(SCTX_ struct statement *stmt)
{
	struct statement  *pre_statement = stmt->iterator_pre_statement;
	struct expression *pre_condition = stmt->iterator_pre_condition;
	struct statement  *statement = stmt->iterator_statement;
	struct statement  *post_statement = stmt->iterator_post_statement;
	struct expression *post_condition = stmt->iterator_post_condition;
	int loop_top = 0, loop_bottom, loop_continue;
	int have_bottom = 0;
	struct storage *val;

	loop_bottom = new_label(sctx);
	loop_continue = new_label(sctx);
	loopstk_push(sctx_ loop_continue, loop_bottom);

	x86_symbol_decl(sctx_ stmt->iterator_syms);
	x86_statement(sctx_ pre_statement);
	if (!post_condition || post_condition->type != EXPR_VALUE || post_condition->value) {
		loop_top = new_label(sctx);
		emit_label(sctx_ loop_top, "loop top");
	}
	if (pre_condition) {
		if (pre_condition->type == EXPR_VALUE) {
			if (!pre_condition->value) {
				struct storage *lbv;
				lbv = new_storage(sctx_ STOR_LABEL);
				lbv->label = loop_bottom;
				lbv->flags = STOR_WANTS_FREE;
				insn(sctx_ "jmp", lbv, NULL, "go to loop bottom");
				have_bottom = 1;
			}
		} else {
			struct storage *lbv = new_storage(sctx_ STOR_LABEL);
			lbv->label = loop_bottom;
			lbv->flags = STOR_WANTS_FREE;
			have_bottom = 1;

			val = x86_expression(sctx_ pre_condition);

			emit_move(sctx_ val, REG_EAX, NULL, "loop pre condition");
			insn(sctx_ "test", REG_EAX, REG_EAX, NULL);
			insn(sctx_ "jz", lbv, NULL, NULL);
		}
	}
	x86_statement(sctx_ statement);
	if (stmt->iterator_continue->used)
		emit_label(sctx_ loop_continue, "'continue' iterator");
	x86_statement(sctx_ post_statement);
	if (!post_condition) {
		struct storage *lbv = new_storage(sctx_ STOR_LABEL);
		lbv->label = loop_top;
		lbv->flags = STOR_WANTS_FREE;
		insn(sctx_ "jmp", lbv, NULL, "go to loop top");
	} else if (post_condition->type == EXPR_VALUE) {
		if (post_condition->value) {
			struct storage *lbv = new_storage(sctx_ STOR_LABEL);
			lbv->label = loop_top;
			lbv->flags = STOR_WANTS_FREE;
			insn(sctx_ "jmp", lbv, NULL, "go to loop top");
		}
	} else {
		struct storage *lbv = new_storage(sctx_ STOR_LABEL);
		lbv->label = loop_top;
		lbv->flags = STOR_WANTS_FREE;

		val = x86_expression(sctx_ post_condition);

		emit_move(sctx_ val, REG_EAX, NULL, "loop post condition");
		insn(sctx_ "test", REG_EAX, REG_EAX, NULL);
		insn(sctx_ "jnz", lbv, NULL, NULL);
	}
	if (have_bottom || stmt->iterator_break->used)
		emit_label(sctx_ loop_bottom, "loop bottom");

	loopstk_pop(sctx);
}

/*
 * Print out a statement
 */
static struct storage *x86_statement(SCTX_ struct statement *stmt)
{
	if (!stmt)
		return NULL;
	switch (stmt->type) {
	default:
		return NULL;
	case STMT_RETURN:
		return emit_return_stmt(sctx_ stmt);
	case STMT_DECLARATION:
		x86_symbol_decl(sctx_ stmt->declaration);
		break;
	case STMT_COMPOUND: {
		struct statement *s;
		struct storage *last = NULL;

		FOR_EACH_PTR(stmt->stmts, s) {
			last = x86_statement(sctx_ s);
		} END_FOR_EACH_PTR(s);

		return last;
	}

	case STMT_EXPRESSION:
		return x86_expression(sctx_ stmt->expression);
	case STMT_IF:
		emit_if_conditional(sctx_ stmt);
		return NULL;

	case STMT_CASE:
		emit_case_statement(sctx_ stmt);
		break;
	case STMT_SWITCH:
		emit_switch_statement(sctx_ stmt);
		break;

	case STMT_ITERATOR:
		emit_loop(sctx_ stmt);
		break;

	case STMT_NONE:
		break;

	case STMT_LABEL:
		printf(".L%p:\n", stmt->label_identifier);
		x86_statement(sctx_ stmt->label_statement);
		break;

	case STMT_GOTO:
		if (stmt->goto_expression) {
			struct storage *val = x86_expression(sctx_ stmt->goto_expression);
			printf("\tgoto *v%d\n", val->pseudo);
		} else if (!strcmp("break", show_ident(sctx_ stmt->goto_label->ident))) {
			struct storage *lbv = new_storage(sctx_ STOR_LABEL);
			lbv->label = loopstk_break(sctx);
			lbv->flags = STOR_WANTS_FREE;
			insn(sctx_ "jmp", lbv, NULL, "'break'; go to loop bottom");
		} else if (!strcmp("continue", show_ident(sctx_ stmt->goto_label->ident))) {
			struct storage *lbv = new_storage(sctx_ STOR_LABEL);
			lbv->label = loopstk_continue(sctx);
			lbv->flags = STOR_WANTS_FREE;
			insn(sctx_ "jmp", lbv, NULL, "'continue'; go to loop top");
		} else {
			struct storage *labelsym = new_labelsym(sctx_ stmt->goto_label);
			insn(sctx_ "jmp", labelsym, NULL, NULL);
		}
		break;
	case STMT_ASM:
		printf("\tasm( .... )\n");
		break;
	}
	return NULL;
}

static struct storage *x86_call_expression(SCTX_ struct expression *expr)
{
	struct function *f = current_func;
	struct symbol *direct;
	struct expression *arg, *fn;
	struct storage *retval, *fncall;
	int framesize;
	char s[64];

	if (!expr->ctype) {
		warning(sctx_ expr->pos->pos, "\tcall with no type!");
		return NULL;
	}

	framesize = 0;
	FOR_EACH_PTR_REVERSE(expr->args, arg) {
		struct storage *new = x86_expression(sctx_ arg);
		int size = arg->ctype->bit_size;

		/*
		 * FIXME: i386 SysV ABI dictates that values
		 * smaller than 32 bits should be placed onto
		 * the stack as 32-bit objects.  We should not
		 * blindly do a 32-bit push on objects smaller
		 * than 32 bits.
		 */
		if (size < 32)
			size = 32;
		insn(sctx_ "pushl", new, NULL,
		     !framesize ? "begin function call" : NULL);

		framesize += bits_to_bytes(sctx_ size);
	} END_FOR_EACH_PTR_REVERSE(arg);

	fn = expr->fn;

	/* Remove dereference, if any */
	direct = NULL;
	if (fn->type == EXPR_PREOP) {
		if (fn->unop->type == EXPR_SYMBOL) {
			struct symbol *sym = fn->unop->symbol;
			if (sym->ctype.base_type->type == SYM_FN)
				direct = sym;
		}
	}
	if (direct) {
		struct storage *direct_stor = new_storage(sctx_ STOR_SYM);
		direct_stor->flags |= STOR_WANTS_FREE;
		direct_stor->sym = direct;
		insn(sctx_ "call", direct_stor, NULL, NULL);
	} else {
		fncall = x86_expression(sctx_ fn);
		emit_move(sctx_ fncall, REG_EAX, fn->ctype, NULL);

		strcpy(s, "\tcall\t*%eax\n");
		push_text_atom(sctx_ f, s);
	}

	/* FIXME: pay attention to BITS_IN_POINTER */
	if (framesize) {
		struct storage *val = new_storage(sctx_ STOR_VALUE);
		val->value = (long long) framesize;
		val->flags = STOR_WANTS_FREE;
		insn(sctx_ "addl", val, REG_ESP, NULL);
	}

	retval = stack_alloc(sctx_ 4);
	emit_move(sctx_ REG_EAX, retval, NULL, "end function call");

	return retval;
}

static struct storage *x86_address_gen(SCTX_ struct expression *expr)
{
	struct function *f = current_func;
	struct storage *addr;
	struct storage *new;
	char s[32];

	addr = x86_expression(sctx_ expr->unop);
	if (expr->unop->type == EXPR_SYMBOL)
		return addr;

	emit_move(sctx_ addr, REG_EAX, NULL, "begin deref ..");

	/* FIXME: operand size */
	strcpy(s, "\tmovl\t(%eax), %ecx\n");
	push_text_atom(sctx_ f, s);

	new = stack_alloc(sctx_ 4);
	emit_move(sctx_ REG_ECX, new, NULL, ".... end deref");

	return new;
}

static struct storage *x86_assignment(SCTX_ struct expression *expr)
{
	struct expression *target = expr->left;
	struct storage *val, *addr;

	if (!expr->ctype)
		return NULL;

	val = x86_expression(sctx_ expr->right);
	addr = x86_address_gen(sctx_ target);

	switch (val->type) {
	/* copy, where both operands are memory */
	case STOR_PSEUDO:
	case STOR_ARG:
		emit_copy(sctx_ addr, val, expr->ctype);
		break;

	/* copy, one or zero operands are memory */
	case STOR_REG:
	case STOR_SYM:
	case STOR_VALUE:
	case STOR_LABEL:
		emit_move(sctx_ val, addr, expr->left->ctype, NULL);
		break;

	case STOR_LABELSYM:
		assert(0);
		break;
	}
	return val;
}

static int x86_initialization(SCTX_ struct symbol *sym, struct expression *expr)
{
	struct storage *val, *addr;
	int bits;

	if (!expr->ctype)
		return 0;

	bits = expr->ctype->bit_size;
	val = x86_expression(sctx_ expr);
	addr = x86_symbol_expr(sctx_ sym);
	// FIXME! The "target" expression is for bitfield store information.
	// Leave it NULL, which works fine.
	emit_store(sctx_ NULL, addr, val, bits);
	return 0;
}

static struct storage *x86_access(SCTX_ struct expression *expr)
{
	return x86_address_gen(sctx_ expr);
}

static struct storage *x86_preop(SCTX_ struct expression *expr)
{
	/*
	 * '*' is an lvalue access, and is fundamentally different
	 * from an arithmetic operation. Maybe it should have an
	 * expression type of its own..
	 */
	if (expr->op == '*')
		return x86_access(sctx_ expr);
	if (expr->op == SPECIAL_INCREMENT || expr->op == SPECIAL_DECREMENT)
		return emit_inc_dec(sctx_ expr, 0);
	return emit_regular_preop(sctx_ expr);
}

static struct storage *x86_symbol_expr(SCTX_ struct symbol *sym)
{
	struct storage *new = stack_alloc(sctx_ 4);

	if (sym->ctype.modifiers & (MOD_TOPLEVEL | MOD_EXTERN | MOD_STATIC)) {
		printf("\tmovi.%d\t\tv%d,$%s\n", sctxp bits_in_pointer, new->pseudo, show_ident(sctx_ sym->ident));
		return new;
	}
	if (sym->ctype.modifiers & MOD_ADDRESSABLE) {
		printf("\taddi.%d\t\tv%d,vFP,$%lld\n", sctxp bits_in_pointer, new->pseudo, sym->value);
		return new;
	}
	printf("\taddi.%d\t\tv%d,vFP,$offsetof(%s:%p)\n", sctxp bits_in_pointer, new->pseudo, show_ident(sctx_ sym->ident), sym);
	return new;
}

static void x86_symbol_init(SCTX_ struct symbol *sym)
{
	struct symbol_private *priv = sym->aux;
	struct expression *expr = sym->initializer;
	struct storage *new;

	if (expr)
		new = x86_expression(sctx_ expr);
	else
		new = stack_alloc(sctx_ sym->bit_size / 8);

	if (!priv) {
		priv = calloc(1, sizeof(*priv));
		sym->aux = priv;
		/* FIXME: leak! we don't free... */
		/* (well, we don't free symbols either) */
	}

	priv->addr = new;
}

static int type_is_signed(SCTX_ struct symbol *sym)
{
	if (sym->type == SYM_NODE)
		sym = sym->ctype.base_type;
	if (sym->type == SYM_PTR)
		return 0;
	return !(sym->ctype.modifiers & MOD_UNSIGNED);
}

static struct storage *x86_label_expr(SCTX_ struct expression *expr)
{
	struct storage *new = stack_alloc(sctx_ 4);
	printf("\tmovi.%d\t\tv%d,.L%p\n", sctxp bits_in_pointer, new->pseudo, expr->label_symbol);
	return new;
}

static struct storage *x86_statement_expr(SCTX_ struct expression *expr)
{
	return x86_statement(sctx_ expr->statement);
}

static int x86_position_expr(SCTX_ struct expression *expr, struct symbol *base)
{
	struct storage *new = x86_expression(sctx_ expr->init_expr);
	struct symbol *ctype = expr->init_expr->ctype;

	printf("\tinsert v%d at [%d:%d] of %s\n", new->pseudo,
		expr->init_offset, ctype->bit_offset,
		show_ident(sctx_ base->ident));
	return 0;
}

static void x86_initializer_expr(SCTX_ struct expression *expr, struct symbol *ctype)
{
	struct expression *entry;

	FOR_EACH_PTR(expr->expr_list, entry) {
		// Nested initializers have their positions already
		// recursively calculated - just output them too
		if (entry->type == EXPR_INITIALIZER) {
			x86_initializer_expr(sctx_ entry, ctype);
			continue;
		}

		// Ignore initializer indexes and identifiers - the
		// evaluator has taken them into account
		if (entry->type == EXPR_IDENTIFIER || entry->type == EXPR_INDEX)
			continue;
		if (entry->type == EXPR_POS) {
			x86_position_expr(sctx_ entry, ctype);
			continue;
		}
		x86_initialization(sctx_ ctype, entry);
	} END_FOR_EACH_PTR(entry);
}

/*
 * Print out an expression. Return the pseudo that contains the
 * variable.
 */
static struct storage *x86_expression(SCTX_ struct expression *expr)
{
	if (!expr)
		return NULL;

	if (!expr->ctype) {
		struct position *pos = &expr->pos->pos;
		printf("\tno type at %s:%d:%d\n",
			stream_name(sctx_ pos->stream),
			pos->line, pos->pos);
		return NULL;
	}

	switch (expr->type) {
	default:
		return NULL;
	case EXPR_CALL:
		return x86_call_expression(sctx_ expr);

	case EXPR_ASSIGNMENT:
		return x86_assignment(sctx_ expr);

	case EXPR_COMPARE:
		return emit_compare(sctx_ expr);
	case EXPR_BINOP:
	case EXPR_COMMA:
	case EXPR_LOGICAL:
		return emit_binop(sctx_ expr);
	case EXPR_PREOP:
		return x86_preop(sctx_ expr);
	case EXPR_POSTOP:
		return emit_postop(sctx_ expr);
	case EXPR_SYMBOL:
		return emit_symbol_expr_init(sctx_ expr->symbol);
	case EXPR_DEREF:
	case EXPR_SIZEOF:
	case EXPR_ALIGNOF:
		warning(sctx_ expr->pos->pos, "invalid expression after evaluation");
		return NULL;
	case EXPR_CAST:
	case EXPR_FORCE_CAST:
	case EXPR_IMPLIED_CAST:
		return emit_cast_expr(sctx_ expr);
	case EXPR_VALUE:
		return emit_value(sctx_ expr);
	case EXPR_STRING:
		return emit_string_expr(sctx_ expr);
	case EXPR_INITIALIZER:
		x86_initializer_expr(sctx_ expr, expr->ctype);
		return NULL;
	case EXPR_SELECT:
		return emit_select_expr(sctx_ expr);
	case EXPR_CONDITIONAL:
		return emit_conditional_expr(sctx_ expr);
	case EXPR_STATEMENT:
		return x86_statement_expr(sctx_ expr);
	case EXPR_LABEL:
		return x86_label_expr(sctx_ expr);

	// None of these should exist as direct expressions: they are only
	// valid as sub-expressions of initializers.
	case EXPR_POS:
		warning(sctx_ expr->pos->pos, "unable to show plain initializer position expression");
		return NULL;
	case EXPR_IDENTIFIER:
		warning(sctx_ expr->pos->pos, "unable to show identifier expression");
		return NULL;
	case EXPR_INDEX:
		warning(sctx_ expr->pos->pos, "unable to show index expression");
		return NULL;
	case EXPR_TYPE:
		warning(sctx_ expr->pos->pos, "unable to show type expression");
		return NULL;
	case EXPR_FVALUE:
		warning(sctx_ expr->pos->pos, "floating point support is not implemented");
		return NULL;
	}
	return NULL;
}
