/*
===========================================================================
vm_x86_64_assembler.c -- assembler for x86-64

Copyright (C) 2007 Ludwig Nussel <ludwig.nussel@suse.de>, Novell inc.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#define _ISOC99_SOURCE

#include "vm_local.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include <inttypes.h>

// Ignore __attribute__ on non-gcc platforms
#ifndef __GNUC__
#ifndef __attribute__
#define __attribute__(x)
#endif
#endif

typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;

static char* out;
static unsigned compiledOfs;
static unsigned assembler_pass;

static const char* cur_line;

static FILE* fout;

#define crap(fmt, args...) do { \
	_crap(__FUNCTION__, fmt, ##args); \
} while(0)

#define CRAP_INVALID_ARGS crap("invalid arguments %s, %s", argtype2str(arg1.type),argtype2str(arg2.type));

#ifdef DEBUG
#define debug(fmt, args...) printf(fmt, ##args)
#else
#define debug(fmt, args...)
#endif

static __attribute__ ((noreturn)) __attribute__ ((format (printf, 2, 3))) void _crap(const char* func, const char* fmt, ...)
{
	va_list ap;
	fprintf(stderr, "%s() - ", func);
	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
	fputc('\n', stderr);
	if(cur_line && cur_line[0])
		fprintf(stderr, "-> %s\n", cur_line);
	exit(1);
}

static void emit1(unsigned char v)
{
	int writecnt;
	
	if(assembler_pass)
	{
		out[compiledOfs++] = v;

		if(fout)
			writecnt = fwrite(&v, 1, 1, fout);
			
		debug("%02hx ", v);
	}
	else
	{
		++compiledOfs;
	}
}

static inline void emit2(u16 v)
{
	emit1(v&0xFF);
	emit1((v>>8)&0xFF);
}

static inline void emit4(u32 v)
{
	emit1(v&0xFF);
	emit1((v>>8)&0xFF);
	emit1((v>>16)&0xFF);
	emit1((v>>24)&0xFF);
}

static inline void emit8(u64 v)
{
	emit4(v&0xFFFFFFFF);
	emit4((v>>32)&0xFFFFFFFF);
}

enum {
	REX_W = 0x08,
	REX_R = 0x04,
	REX_X = 0x02,
	REX_B = 0x01,
};

enum {
	MODRM_MOD_00 = 0x00,
	MODRM_MOD_01 = 0x01 << 6,
	MODRM_MOD_10 = 0x02 << 6,
	MODRM_MOD_11 = 0x03 << 6,
	MODRM_RM_SIB = 0x04,
};

typedef enum
{
	T_NONE      = 0x00,
	T_REGISTER  = 0x01,
	T_IMMEDIATE = 0x02,
	T_MEMORY    = 0x04,
	T_LABEL     = 0x08,
	T_ABSOLUTE  = 0x80
} argtype_t;

typedef enum {
	R_8   = 0x100, 
	R_16  = 0x200, 
	R_64  = 0x800, 
	R_MSZ = 0xF00,  // size mask
	R_XMM = 0x2000, // xmm register. year, sucks
	R_EAX =  0x00,
	R_EBX =  0x03,
	R_ECX =  0x01,
	R_EDX =  0x02,
	R_ESI =  0x06,
	R_EDI =  0x07,
	R_ESP =  0x04,
	R_RAX =  R_EAX | R_64,
	R_RBX =  R_EBX | R_64,
	R_RCX =  R_ECX | R_64,
	R_RDX =  R_EDX | R_64,
	R_RSI =  R_ESI | R_64,
	R_RDI =  R_EDI | R_64,
	R_RSP =  R_ESP | R_64,
	R_R8  =  0x08  | R_64,
	R_R9  =  0x09  | R_64,
	R_R10 =  0x0A  | R_64,
	R_R15 =  0x0F  | R_64,
	R_AL  =  R_EAX | R_8,
	R_AX  =  R_EAX | R_16,
	R_BL  =  R_EBX | R_8,
	R_CL  =  R_ECX | R_8,
	R_XMM0 = 0x00  | R_XMM,
	R_MGP =  0x0F, // mask for general purpose registers
} reg_t;

typedef enum {
	MODRM_SIB = 0,
	MODRM_NOSIB = 0x3,
} modrm_sib_t;

typedef struct {
	unsigned disp;
	argtype_t basetype;
	union {
		u64 imm;
		reg_t reg;
	} base;
	argtype_t indextype;
	union {
		u64 imm;
		reg_t reg;
	} index;
	unsigned scale;
} memref_t;

#define LABELLEN 32

typedef struct {
	argtype_t type;
	union {
		u64 imm;
		reg_t reg;
		memref_t mem;
		char label[LABELLEN];
	} v;
	int absolute:1;
} arg_t;

typedef void (*emitfunc)(const char* op, arg_t arg1, arg_t arg2, void* data);

typedef struct {
	char* mnemonic;
	emitfunc func;
	void* data;
} op_t;

typedef struct {
	u8 xmmprefix;
	u8 subcode; // in modrm
	u8 rmcode;  // opcode for reg/mem, reg
	u8 mrcode;  // opcode for reg, reg/mem
	u8 rcode8;  // opcode for reg8/mem8
	u8 rcode;  // opcode for reg/mem
} opparam_t;

static opparam_t params_add = { subcode: 0, rmcode: 0x01, };
static opparam_t params_or = { subcode: 1, rmcode: 0x09, };
static opparam_t params_and = { subcode: 4, rmcode: 0x21, };
static opparam_t params_sub = { subcode: 5, rmcode: 0x29, };
static opparam_t params_xor = { subcode: 6, rmcode: 0x31, };
static opparam_t params_cmp = { subcode: 7, rmcode: 0x39, mrcode: 0x3b, };
static opparam_t params_dec = { subcode: 1, rcode: 0xff, rcode8: 0xfe, };
static opparam_t params_sar = { subcode: 7, rcode: 0xd3, rcode8: 0xd2, };
static opparam_t params_shl = { subcode: 4, rcode: 0xd3, rcode8: 0xd2, };
static opparam_t params_shr = { subcode: 5, rcode: 0xd3, rcode8: 0xd2, };
static opparam_t params_idiv = { subcode: 7, rcode: 0xf7, rcode8: 0xf6, };
static opparam_t params_div = { subcode: 6, rcode: 0xf7, rcode8: 0xf6, };
static opparam_t params_imul = { subcode: 5, rcode: 0xf7, rcode8: 0xf6, };
static opparam_t params_mul = { subcode: 4, rcode: 0xf7, rcode8: 0xf6, };
static opparam_t params_neg = { subcode: 3, rcode: 0xf7, rcode8: 0xf6, };
static opparam_t params_not = { subcode: 2, rcode: 0xf7, rcode8: 0xf6, };

static opparam_t params_cvtsi2ss = { xmmprefix: 0xf3, rmcode: 0x2a };
static opparam_t params_cvttss2si = { xmmprefix: 0xf3, rmcode: 0x2c };
static opparam_t params_addss = { xmmprefix: 0xf3, mrcode: 0x58 };
static opparam_t params_divss = { xmmprefix: 0xf3, mrcode: 0x5e };
static opparam_t params_movss = { xmmprefix: 0xf3, mrcode: 0x10, rmcode: 0x11 };
static opparam_t params_mulss = { xmmprefix: 0xf3, mrcode: 0x59 };
static opparam_t params_subss = { xmmprefix: 0xf3, mrcode: 0x5c };
static opparam_t params_ucomiss = { mrcode: 0x2e };

/* ************************* */

static unsigned hashkey(const char *string, unsigned len) {
	unsigned register hash, i;

	hash = 0;
	for (i = 0; i < len && string[i] != '\0'; ++i) {
		hash += string[i] * (119 + i);
	}
	hash = (hash ^ (hash >> 10) ^ (hash >> 20));
	return hash;
}

struct hashentry {
	char* label;
	unsigned address;
	struct hashentry* next;
};
static struct hashentry* labelhash[1021];

// no dup check!
static void hash_add_label(const char* label, unsigned address)
{
	struct hashentry* h;
	unsigned i = hashkey(label, -1U);
	int labellen;
	
	i %= ARRAY_LEN(labelhash);
	h = Z_Malloc(sizeof(struct hashentry));
	
	labellen = strlen(label) + 1;
	h->label = Z_Malloc(labellen);
	memcpy(h->label, label, labellen);
	
	h->address = address;
	h->next = labelhash[i];
	labelhash[i] = h;
}

static unsigned lookup_label(const char* label)
{
	struct hashentry* h;
	unsigned i = hashkey(label, -1U);
	i %= ARRAY_LEN(labelhash);
	for(h = labelhash[i]; h; h = h->next )
	{
		if(!strcmp(h->label, label))
			return h->address;
	}
	if(assembler_pass)
		crap("label %s undefined", label);
	return 0;
}

static void labelhash_free(void)
{
	struct hashentry* h;
	unsigned i;
	unsigned z = 0, min = -1U, max = 0, t = 0;
	for ( i = 0; i < ARRAY_LEN(labelhash); ++i)
	{
		unsigned n = 0;
		h = labelhash[i];
		while(h)
		{
			struct hashentry* next = h->next;
			Z_Free(h->label);
			Z_Free(h);
			h = next;
			++n;
		}
		t+=n;
		if(!n) ++z;
		//else printf("%u\n", n);
		min = MIN(min, n);
		max = MAX(max, n);
	}
	Com_DPrintf("total %u, hsize %"PRIu64", zero %u, min %u, max %u\n", t, ARRAY_LEN(labelhash), z, min, max);
	memset(labelhash, 0, sizeof(labelhash));
}

/* ************************* */


static const char* argtype2str(argtype_t t)
{
	switch(t)
	{
		case T_NONE: return "none";
		case T_REGISTER: return "register";
		case T_IMMEDIATE: return "immediate";
		case T_MEMORY: return "memory";
		case T_LABEL: return "label";
		default: crap("invalid type");
	}
	/* not reached */
	return T_NONE;
}

/* ************************* */

static inline int iss8(int64_t v)
{
	return (SCHAR_MIN <= v && v <= SCHAR_MAX);
}

static inline int isu8(u64 v)
{
	return (v <= UCHAR_MAX);
}

static inline int iss16(int64_t v)
{
	return (SHRT_MIN <= v && v <= SHRT_MAX);
}

static inline int isu16(u64 v)
{
	return (v <= USHRT_MAX);
}

static inline int iss32(int64_t v)
{
	return (INT_MIN <= v && v <= INT_MAX);
}

static inline int isu32(u64 v)
{
	return (v <= UINT_MAX);
}

static void emit_opsingle(const char* mnemonic, arg_t arg1, arg_t arg2, void* data)
{
	u8 op = (u8)((uint64_t) data);

	if(arg1.type != T_NONE || arg2.type != T_NONE)
		CRAP_INVALID_ARGS;

	emit1(op);
}

static void emit_opsingle16(const char* mnemonic, arg_t arg1, arg_t arg2, void* data)
{
	emit1(0x66);
	emit_opsingle(mnemonic, arg1, arg2, data);
}

static void compute_rexmodrmsib(u8* rex_r, u8* modrm_r, u8* sib_r, arg_t* arg1, arg_t* arg2)
{
	u8 rex = 0;
	u8 modrm = 0;
	u8 sib = 0;

	if((arg1->type == T_REGISTER && arg2->type == T_REGISTER)
	&& ((arg1->v.reg & R_MSZ) != (arg2->v.reg & R_MSZ))
	&& !((arg1->v.reg & R_XMM) || (arg2->v.reg & R_XMM)))
		crap("both registers must be of same width");

	if((arg1->type == T_REGISTER && arg1->v.reg & R_64)
	|| (arg2->type == T_REGISTER && arg2->v.reg & R_64))
	{
		rex |= REX_W;
	}

	if(arg1->type == T_REGISTER)
	{
		if((arg1->v.reg & R_MGP) > 0x07)
			rex |= REX_R;

		modrm |= (arg1->v.reg & 0x07) << 3;
	}

	if(arg2->type == T_REGISTER)
	{
		if((arg2->v.reg & R_MGP) > 0x07)
			rex |= REX_B;

		modrm |= (arg2->v.reg & 0x07);
	}

	if(arg2->type == T_MEMORY)
	{
		if((arg2->v.mem.basetype == T_REGISTER && !(arg2->v.mem.base.reg & R_64))
		|| (arg2->v.mem.indextype == T_REGISTER && !(arg2->v.mem.index.reg & R_64)))
		{
			crap("only 64bit base/index registers are %x %x", arg2->v.mem.base.reg, arg2->v.mem.index.reg);
		}

		if(arg2->v.mem.indextype == T_REGISTER)
		{
			modrm |= MODRM_RM_SIB;
			if(!arg2->v.mem.disp)
			{
				modrm |= MODRM_MOD_00;
			}
			else if(iss8(arg2->v.mem.disp))
			{
				modrm |= MODRM_MOD_01;
			}
			else if(isu32(arg2->v.mem.disp))
			{
				modrm |= MODRM_MOD_10;
			}
			else
			{
				crap("invalid displacement");
			}

			if((arg2->v.mem.index.reg & R_MGP) > 0x07)
				rex |= REX_X;

			if((arg2->v.mem.base.reg & R_MGP) > 0x07)
				rex |= REX_B;

			if(arg2->v.mem.basetype != T_REGISTER)
				crap("base must be register");
			switch(arg2->v.mem.scale)
			{
				case 1: break;
				case 2: sib |= 1 << 6; break;
				case 4: sib |= 2 << 6; break;
				case 8: sib |= 3 << 6; break;
			}
			sib |= (arg2->v.mem.index.reg & 0x07) << 3;
			sib |= (arg2->v.mem.base.reg & 0x07);
		}
		else if(arg2->v.mem.indextype == T_NONE)
		{
			if(!arg2->v.mem.disp)
			{
				modrm |= MODRM_MOD_00;
			}
			else if(iss8(arg2->v.mem.disp))
			{
				modrm |= MODRM_MOD_01;
			}
			else if(isu32(arg2->v.mem.disp))
			{
				modrm |= MODRM_MOD_10;
			}
			else
			{
				crap("invalid displacement");
			}

			if(arg2->v.mem.basetype != T_REGISTER)
				crap("todo: base != register");

			if((arg2->v.mem.base.reg & R_MGP) > 0x07)
				rex |= REX_B;

			modrm |= arg2->v.mem.base.reg & 0x07;
		}
		else
		{
			crap("invalid indextype");
		}
	}
	else
	{
		modrm |= MODRM_MOD_11;
	}

	if(rex)
		rex |= 0x40; // XXX

	*rex_r = rex;
	*modrm_r = modrm;
	*sib_r = sib;
}

static void maybe_emit_displacement(arg_t* arg)
{
	if(arg->type != T_MEMORY)
		return;

	if(arg->v.mem.disp)
	{
		if(iss8(arg->v.mem.disp))
		{
			emit1((u8)arg->v.mem.disp);
		}
		else if(isu32(arg->v.mem.disp))
		{
			emit4(arg->v.mem.disp);
		}
		else
		{
			crap("invalid displacement");
		}
	}
}

/* one byte operator with register added to operator */
static void emit_opreg(const char* mnemonic, arg_t arg1, arg_t arg2, void* data)
{
	u8 op = (u8)((uint64_t) data);

	if(arg1.type != T_REGISTER || arg2.type != T_NONE)
		CRAP_INVALID_ARGS;

	if((arg1.v.reg & R_MGP) > 0x07)
		emit1(0x40 | REX_B);

	op |= (arg1.v.reg & 0x07);

	emit1(op);
}

/* operator which operates on reg/mem */
static void emit_op_rm(const char* mnemonic, arg_t arg1, arg_t arg2, void* data)
{
	u8 rex, modrm, sib;
	opparam_t* params = data;

	if((arg1.type != T_REGISTER && arg1.type != T_MEMORY) || arg2.type != T_NONE)
		CRAP_INVALID_ARGS;

	compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1);

	modrm |= params->subcode << 3;

	if(arg1.v.reg & R_16)
		emit1(0x66);

	if(rex) emit1(rex);
	if(arg1.v.reg & R_8)
		emit1(params->rcode8); // op reg8/mem8,
	else
		emit1(params->rcode); // op reg/mem,
	emit1(modrm);
	if((modrm & 0x07) == MODRM_RM_SIB)
		emit1(sib);

	maybe_emit_displacement(&arg1);
}

/* operator which operates on reg/mem with cl */
static void emit_op_rm_cl(const char* mnemonic, arg_t arg1, arg_t arg2, void* data)
{
	u8 rex, modrm, sib;
	opparam_t* params = data;

	if(arg2.type != T_REGISTER || arg1.type != T_REGISTER)
		CRAP_INVALID_ARGS;

	if((arg1.v.reg & R_MGP) != R_ECX && !(arg1.v.reg & R_8))
		crap("only cl register is valid");

	arg1.type = T_NONE; // don't complain, we know it's cl anyways

	compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2);

	modrm |= params->subcode << 3;

	if(arg2.v.reg & R_16)
		emit1(0x66);

	if(rex) emit1(rex);
	if(arg2.v.reg & R_8)
		emit1(params->rcode8); // op reg8/mem8,
	else
		emit1(params->rcode); // op reg/mem,
	emit1(modrm);
	if((modrm & 0x07) == MODRM_RM_SIB)
		emit1(sib);

	maybe_emit_displacement(&arg2);
}

static void emit_mov(const char* mnemonic, arg_t arg1, arg_t arg2, void* data)
{
	u8 rex = 0;
	u8 modrm = 0;
	u8 sib = 0;

	if(arg1.type == T_IMMEDIATE && arg2.type == T_REGISTER)
	{
		u8 op = 0xb8;
		
		if(arg2.v.reg & R_8)
		{
			if(!iss8(arg1.v.imm))
				crap("value too large for 8bit register");

			op = 0xb0;
		}
		else if(arg2.v.reg & R_16)
		{
			if(!isu16(arg1.v.imm))
				crap("value too large for 16bit register");
			emit1(0x66);
		}
		else if(!(arg2.v.reg & R_64))
		{
			if(!isu32(arg1.v.imm))
				crap("value too large for 32bit register");
		}

		compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2);

		if(rex) emit1(rex);

		op |= (arg2.v.reg & 0x07);

		emit1(op);

		if(arg2.v.reg & R_8) emit1(arg1.v.imm);
		else if(arg2.v.reg & R_16) emit2(arg1.v.imm);
		else if(arg2.v.reg & R_64) emit8(arg1.v.imm);
		else emit4(arg1.v.imm);
	}
	else if(arg1.type == T_IMMEDIATE && arg2.type == T_MEMORY)
	{
		if(!iss32(arg1.v.imm))
		{
			crap("only 32bit immediates supported");
		}

		compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2);
		if(rex) emit1(rex);
		emit1(0xc7); // mov reg/mem, imm
		emit1(modrm);
		if((modrm & 0x07) == MODRM_RM_SIB)
			emit1(sib);

		emit4(arg1.v.imm);
	}
	else if(arg1.type == T_REGISTER && arg2.type == T_REGISTER) // XXX: same as next
	{
		if(arg1.type != T_REGISTER || arg2.type != T_REGISTER)
			crap("both args must be registers");

		if((arg1.v.reg & R_MSZ) != (arg2.v.reg & R_MSZ))
			crap("both registers must be same width");

		compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2);

		if(rex) emit1(rex);
		emit1(0x89); // mov reg reg/mem,
		emit1(modrm);
	}
	else if(arg1.type == T_REGISTER && arg2.type == T_MEMORY)
	{
		compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2);

		if(arg1.v.reg & R_16)
			emit1(0x66);

		if(rex) emit1(rex);
		if(arg1.v.reg & R_8)
			emit1(0x88); // mov reg reg/mem,
		else
			emit1(0x89); // mov reg reg/mem,
		emit1(modrm);
		if((modrm & 0x07) == MODRM_RM_SIB)
			emit1(sib);

		maybe_emit_displacement(&arg2);
	}
	else if(arg1.type == T_MEMORY && arg2.type == T_REGISTER)
	{
		compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1);

		if(arg2.v.reg & R_16)
			emit1(0x66);

		if(rex) emit1(rex);
		if(arg2.v.reg & R_8)
			emit1(0x8a); // mov reg/mem, reg
		else
			emit1(0x8b); // mov reg/mem, reg
		emit1(modrm);
		if((modrm & 0x07) == MODRM_RM_SIB)
			emit1(sib);

		maybe_emit_displacement(&arg1);
	}
	else
		CRAP_INVALID_ARGS;
}

static void emit_subaddand(const char* mnemonic, arg_t arg1, arg_t arg2, void* data)
{
	u8 rex = 0;
	u8 modrm = 0;
	u8 sib = 0;

	opparam_t* params = data;

	if(arg1.type == T_IMMEDIATE && arg2.type == T_REGISTER)
	{
		if(!iss32(arg1.v.imm))
		{
			crap("only 8 and 32 bit immediates supported");
		}

		compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2);
		modrm |= params->subcode << 3;

		if(rex) emit1(rex);

		if(arg2.v.reg & R_8)
		{
			emit1(0x80); // sub reg8/mem8, imm8
			emit1(modrm);
			emit1(arg1.v.imm & 0xFF);
		}
		else if(iss8(arg1.v.imm))
		{
			emit1(0x83); // sub reg/mem, imm8
			emit1(modrm);
			emit1(arg1.v.imm & 0xFF);
		}
		else
		{
			emit1(0x81); // sub reg/mem, imm32
			emit1(modrm);
			emit4(arg1.v.imm);
		}
	}
	else if(arg1.type == T_REGISTER && (arg2.type == T_MEMORY || arg2.type == T_REGISTER))
	{
		compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2);

		if(rex) emit1(rex);
		emit1(params->rmcode); // sub reg/mem, reg
		emit1(modrm);
		if(arg2.type == T_MEMORY && (modrm & 0x07) == MODRM_RM_SIB)
			emit1(sib);

		maybe_emit_displacement(&arg2);
	}
	else if(arg1.type == T_MEMORY && arg2.type == T_REGISTER && params->mrcode)
	{
		compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1);

		if(rex) emit1(rex);
		emit1(params->mrcode); // sub reg, reg/mem
		emit1(modrm);
		if((modrm & 0x07) == MODRM_RM_SIB)
			emit1(sib);

		maybe_emit_displacement(&arg1);
	}
	else
		CRAP_INVALID_ARGS;
}

static void emit_condjump(const char* mnemonic, arg_t arg1, arg_t arg2, void* data)
{
	unsigned off;
	int disp;
	unsigned char opcode = (unsigned char)(((uint64_t)data)&0xFF);

	if(arg1.type != T_LABEL || arg2.type != T_NONE)
		crap("%s: argument must be label", mnemonic);

	emit1(opcode);

	off = lookup_label(arg1.v.label);
	disp = off-(compiledOfs+1);
	if(assembler_pass && abs(disp) > 127)
		crap("cannot jump that far (%x -> %x = %x)", compiledOfs, off, disp);

	emit1(disp);
}

static void emit_jmp(const char* mnemonic, arg_t arg1, arg_t arg2, void* data)
{
	if((arg1.type != T_LABEL && arg1.type != T_REGISTER && arg1.type != T_MEMORY) || arg2.type != T_NONE)
		CRAP_INVALID_ARGS;

	if(arg1.type == T_LABEL)
	{
		unsigned off;
		int disp;

		off = lookup_label(arg1.v.label);
		disp = off-(compiledOfs+5);
		emit1(0xe9);
		emit4(disp);
	}
	else
	{
		u8 rex, modrm, sib;

		if(arg1.type == T_REGISTER)
		{
			if(!arg1.absolute)
				crap("jmp must be absolute");

			if((arg1.v.reg & R_64) != R_64)
				crap("register must be 64bit");

			arg1.v.reg ^= R_64; // no rex required for call
		}

		compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1);

		modrm |= 0x4 << 3;

		if(rex) emit1(rex);
		emit1(0xff);
		emit1(modrm);
		if((modrm & 0x07) == MODRM_RM_SIB)
			emit1(sib);
		maybe_emit_displacement(&arg1);
	}
}

static void emit_call(const char* mnemonic, arg_t arg1, arg_t arg2, void* data)
{
	u8 rex, modrm, sib;

	if((arg1.type != T_REGISTER && arg1.type != T_IMMEDIATE) || arg2.type != T_NONE)
		CRAP_INVALID_ARGS;

	if(arg1.type == T_REGISTER)
	{
		if(!arg1.absolute)
			crap("call must be absolute");

		if((arg1.v.reg & R_64) != R_64)
			crap("register must be 64bit");

		arg1.v.reg ^= R_64; // no rex required for call

		compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1);

		modrm |= 0x2 << 3;

		if(rex) emit1(rex);
		emit1(0xff);
		emit1(modrm);
	}
	else 
	{
		if(!isu32(arg1.v.imm))
			crap("must be 32bit argument");
		emit1(0xe8);
		emit4(arg1.v.imm);
	}
}


static void emit_twobyte(const char* mnemonic, arg_t arg1, arg_t arg2, void* data)
{
	u8 rex, modrm, sib;

	opparam_t* params = data;

	if(arg1.type == T_REGISTER && (arg2.type == T_MEMORY || arg2.type == T_REGISTER))
	{
		compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2);

		if(params->xmmprefix) emit1(params->xmmprefix);
		if(rex) emit1(rex);
		emit1(0x0f);
		emit1(params->rmcode); // sub reg/mem, reg
		emit1(modrm);
		if((modrm & 0x07) == MODRM_RM_SIB)
			emit1(sib);

		maybe_emit_displacement(&arg2);
	}
	else if(arg1.type == T_MEMORY && arg2.type == T_REGISTER && params->mrcode)
	{
		compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1);

		if(params->xmmprefix) emit1(params->xmmprefix);
		if(rex) emit1(rex);
		emit1(0x0f);
		emit1(params->mrcode); // sub reg, reg/mem
		emit1(modrm);
		if((modrm & 0x07) == MODRM_RM_SIB)
			emit1(sib);

		maybe_emit_displacement(&arg1);
	}
	else
		CRAP_INVALID_ARGS;
}

static int ops_sorted = 0;
static op_t ops[] = {
	{ "addb", emit_subaddand, &params_add },
	{ "addl", emit_subaddand, &params_add },
	{ "addq", emit_subaddand, &params_add },
	{ "addss", emit_twobyte, &params_addss },
	{ "andb", emit_subaddand, &params_and },
	{ "andl", emit_subaddand, &params_and },
	{ "andq", emit_subaddand, &params_and },
	{ "callq", emit_call, NULL },
	{ "cbw", emit_opsingle16, (void*)0x98 },
	{ "cdq", emit_opsingle, (void*)0x99 },
	{ "cmpb", emit_subaddand, &params_cmp },
	{ "cmpl", emit_subaddand, &params_cmp },
	{ "cmpq", emit_subaddand, &params_cmp },
	{ "cvtsi2ss", emit_twobyte, &params_cvtsi2ss },
	{ "cvttss2si", emit_twobyte, &params_cvttss2si },
	{ "cwde", emit_opsingle, (void*)0x98 },
	{ "decl", emit_op_rm, &params_dec },
	{ "decq", emit_op_rm, &params_dec },
	{ "divl", emit_op_rm, &params_div },
	{ "divq", emit_op_rm, &params_div },
	{ "divss", emit_twobyte, &params_divss },
	{ "idivl", emit_op_rm, &params_idiv },
	{ "imull", emit_op_rm, &params_imul },
	{ "int3", emit_opsingle, (void*)0xcc },
	{ "ja", emit_condjump, (void*)0x77 },
	{ "jbe", emit_condjump, (void*)0x76 },
	{ "jb", emit_condjump, (void*)0x72 },
	{ "je", emit_condjump, (void*)0x74 },
	{ "jl", emit_condjump, (void*)0x7c },
	{ "jmp", emit_jmp, NULL },
	{ "jmpq", emit_jmp, NULL },
	{ "jnae", emit_condjump, (void*)0x72 },
	{ "jna", emit_condjump, (void*)0x76 },
	{ "jnbe", emit_condjump, (void*)0x77 },
	{ "jnb", emit_condjump, (void*)0x73 },
	{ "jnc", emit_condjump, (void*)0x73 },
	{ "jne", emit_condjump, (void*)0x75 },
	{ "jnge", emit_condjump, (void*)0x7c },
	{ "jng", emit_condjump, (void*)0x7e },
	{ "jnle", emit_condjump, (void*)0x7f },
	{ "jnl", emit_condjump, (void*)0x7d },
	{ "jnz", emit_condjump, (void*)0x75 },
	{ "jp", emit_condjump, (void*)0x7a },
	{ "jz", emit_condjump, (void*)0x74 },
	{ "movb", emit_mov, NULL },
	{ "movl", emit_mov, NULL },
	{ "movq", emit_mov, NULL },
	{ "movss", emit_twobyte, &params_movss },
	{ "movw", emit_mov, NULL },
	{ "mull", emit_op_rm, &params_mul },
	{ "mulss", emit_twobyte, &params_mulss },
	{ "negl", emit_op_rm, &params_neg },
	{ "negq", emit_op_rm, &params_neg },
	{ "nop", emit_opsingle, (void*)0x90 },
	{ "notl", emit_op_rm, &params_not },
	{ "notq", emit_op_rm, &params_not },
	{ "orb",   emit_subaddand, &params_or },
	{ "orl",  emit_subaddand, &params_or },
	{ "orq",  emit_subaddand, &params_or },
	{ "pop", emit_opreg, (void*)0x58 },
	{ "push", emit_opreg, (void*)0x50 },
	{ "ret", emit_opsingle, (void*)0xc3 },
	{ "sarl", emit_op_rm_cl, &params_sar },
	{ "shl", emit_op_rm_cl, &params_shl },
	{ "shrl", emit_op_rm_cl, &params_shr },
	{ "subb", emit_subaddand, &params_sub },
	{ "subl", emit_subaddand, &params_sub },
	{ "subq", emit_subaddand, &params_sub },
	{ "subss", emit_twobyte, &params_subss },
	{ "ucomiss", emit_twobyte, &params_ucomiss },
	{ "xorb",  emit_subaddand, &params_xor },
	{ "xorl",  emit_subaddand, &params_xor },
	{ "xorq",  emit_subaddand, &params_xor },
	{ NULL, NULL, NULL }
};

static int opsort(const void* A, const void* B)
{
	const op_t* a = A;
	const op_t* b = B;
	return strcmp(a->mnemonic, b->mnemonic);
}

static op_t* getop(const char* n)
{
#if 0
	op_t* o = ops;
	while(o->mnemonic)
	{
		if(!strcmp(o->mnemonic, n))
			return o;
		++o;
	}

#else
	unsigned int m, t, b;
	int r;
	t = ARRAY_LEN(ops)-1;

	r = m = -1;

	do
	{
		if(r < 0)
			b = m + 1;
		else
			t = m - 1;

		m = ((t - b) >> 1) + b;

		if((r = strcmp(ops[m].mnemonic, n)) == 0)
			return &ops[m];
	} while(b <= t && t);
#endif

	return NULL;
}

static reg_t parsereg(const char* str)
{
	const char* s = str;
	if(*s == 'a' && s[1] == 'l' && !s[2])
	{
		return R_AL;
	}
	else if(*s == 'a' && s[1] == 'x' && !s[2])
	{
		return R_AX;
	}
	if(*s == 'b' && s[1] == 'l' && !s[2])
	{
		return R_BL;
	}
	if(*s == 'c' && s[1] == 'l' && !s[2])
	{
		return R_CL;
	}
	if(*s == 'x')
	{
		if(!strcmp(s, "xmm0"))
			return R_XMM0;
	}
	else if(*s == 'r' && s[1])
	{
		++s;
		if(s[1] == 'x')
		{
			switch(*s++)
			{
				case 'a': return R_RAX;
				case 'b': return R_RBX;
				case 'c': return R_RCX;
				case 'd': return R_RDX;
			}
		}
		else if(s[1] == 'i')
		{
			switch(*s++)
			{
				case 's': return R_RSI;
				case 'd': return R_RDI;
			}
		}
		else if(s[0] == 's' && s[1] == 'p' && !s[2])
		{
			return R_RSP;
		}
		else if(*s == '8' && !s[1])
			return R_R8;
		else if(*s == '9' && !s[1])
			return R_R9;
		else if(*s == '1' && s[1] == '0')
			return R_R10;
		else if(*s == '1' && s[1] == '5')
			return R_R15;
	}
	else if(*s == 'e' && s[1])
	{
		++s;
		if(s[1] == 'x')
		{
			switch(*s++)
			{
				case 'a': return R_EAX;
				case 'b': return R_EBX;
				case 'c': return R_ECX;
				case 'd': return R_EDX;
			}
		}
		else if(s[1] == 'i')
		{
			switch(*s++)
			{
				case 's': return R_ESI;
				case 'd': return R_EDI;
			}
		}
	}

	crap("invalid register %s", str);

	return 0;
}

typedef enum {
	TOK_LABEL = 0x80,
	TOK_INT = 0x81,
	TOK_END = 0x82,
	TOK_INVALID = 0x83,
} token_t;

static unsigned char nexttok(const char** str, char* label, u64* val)
{
	const char* s = *str;

	if(label) *label = 0;
	if(val) *val = 0;

	while(*s && *s == ' ') ++s;

	if(!*s)
	{
		return TOK_END;
	}
	else if(*s == '$' || *s == '*' || *s == '%' || *s == '-' || *s == ')' || *s == '(' || *s == ',')
	{
		*str = s+1;
		return *s;
	}
	else if(*s >= 'a' && *s <= 'z')
	{
		size_t a = strspn(s+1, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_");
		if(a+1 >= LABELLEN)
			crap("label %s too long", s);
		if(label)
		{
			strncpy(label, s, a+1);
			label[a+1] = 0;
		}
		*str = s+a+1;
		return TOK_LABEL;
	}
	else if(*s >= '0' && *s <= '9')
	{
		char* endptr = NULL;
		u64 v = strtoull(s, &endptr, 0);
		if(endptr && (endptr-s == 0))
			crap("invalid integer %s", s);
		if(val) *val = v;
		*str = endptr;
		return TOK_INT;
	}
	crap("can't parse '%s'", *str);
	return TOK_INVALID;
}

static arg_t parsearg(const char** str)
{
	arg_t arg;
	const char* s = *str;
	char label[20];
	u64 val;
	int negative = 1;
	unsigned ttype;

	arg.type = T_NONE;
	arg.absolute = 0;

	while(*s && *s == ' ') ++s;

	switch(nexttok(&s, label, &val))
	{
		case '$' :
			ttype = nexttok(&s, NULL, &val);
			if(ttype == '-')
			{
				negative = -1;
				ttype = nexttok(&s, NULL, &val);
			}
			if(ttype != TOK_INT)
				crap("expected integer");
			arg.type = T_IMMEDIATE;
			arg.v.imm = negative * val;
			break;
		case '*' :
			if((ttype = nexttok(&s, NULL, NULL)) != '%')
			{
				if(ttype == '(')
					goto tok_memory;
				crap("expected '%%'");
			}
			arg.absolute = 1;
			/* fall through */
		case '%' :
			if(nexttok(&s, label, &val) != TOK_LABEL)
				crap("expected label");
			arg.type = T_REGISTER;
			arg.v.reg = parsereg(label);
			break;
		case TOK_LABEL:
			arg.type = T_LABEL;
			strncpy(arg.v.label, label, LABELLEN);
			break;
		case '-':
			negative = -1;
			if(nexttok(&s, NULL, &val) != TOK_INT)
				crap("expected integer");
			/* fall through */
		case TOK_INT:
			if(nexttok(&s, label, NULL) != '(')
				crap("expected '('"); // mov to/from fixed address not supported
			/* fall through */
		case '(':
tok_memory:
			arg.type = T_MEMORY;
			arg.v.mem.indextype = T_NONE;
			arg.v.mem.disp = negative * val;
			ttype = nexttok(&s, label, &val);
			if(ttype == '%' && nexttok(&s, label, &val) != TOK_LABEL)
			{
				crap("expected register");
			}
			if (ttype == '%')
			{
				arg.v.mem.basetype = T_REGISTER;
				arg.v.mem.base.reg = parsereg(label);
			}
			else if (ttype == TOK_INT)
			{
				arg.v.mem.basetype = T_IMMEDIATE;
				arg.v.mem.base.imm = val;
			}
			if((ttype = nexttok(&s, NULL, NULL)) == ',')
			{
				ttype = nexttok(&s, label, &val);
				if(ttype == '%' && nexttok(&s, label, &val) != TOK_LABEL)
				{
					crap("expected register");
				}
				if (ttype == '%')
				{
					arg.v.mem.indextype = T_REGISTER;
					arg.v.mem.index.reg = parsereg(label);
				}
				else if (ttype == TOK_INT)
				{
					crap("index must be register");
					arg.v.mem.indextype = T_IMMEDIATE;
					arg.v.mem.index.imm = val;
				}
				if(nexttok(&s, NULL, NULL) != ',')
					crap("expected ','");
				if(nexttok(&s, NULL, &val) != TOK_INT)
					crap("expected integer");
				if(val != 1 && val != 2 && val != 4 && val != 8)
					crap("scale must 1, 2, 4 or 8");
				arg.v.mem.scale = val;

				ttype = nexttok(&s, NULL, NULL);
			}
			if(ttype != ')')
			{
				crap("expected ')' or ','");
			}
			break;
		default:
			crap("invalid token %hu in %s", *(unsigned char*)s, *str);
			break;
	}

	*str = s;

	return arg;
}

/* ************************* */

void assembler_init(int pass)
{
	compiledOfs = 0;
	assembler_pass = pass;
	if(!pass)
	{
		labelhash_free();
		cur_line = NULL;
	}
	if(!ops_sorted)
	{
		ops_sorted = 1;
		qsort(ops, ARRAY_LEN(ops)-1, sizeof(ops[0]), opsort);
	}
}

size_t assembler_get_code_size(void)
{
	return compiledOfs;
}

void assembler_set_output(char* buf)
{
	out = buf;
}

void assemble_line(const char* input, size_t len)
{
	char line[4096];
	char* s;
	op_t* o;
	char* opn;
	arg_t arg1, arg2;

	arg1.type = T_NONE;
	arg2.type = T_NONE;
	opn = NULL;
	o = NULL;

	if(len < 1)
		return;

	if(len >= sizeof(line))
		crap("line too long");

	memcpy(line, input, sizeof(line));
	cur_line = input;

	if(line[len-1] == '\n') line[--len] = 0;
	if(line[len-1] == ':')
	{
		line[--len] = 0;
		if(assembler_pass)
			debug("%s: 0x%x\n", line, compiledOfs);
		else
			hash_add_label(line, compiledOfs);
	}
	else
	{
		opn = line;
		s = strchr(line, ' ');
		if(s)
		{
			*s++ = 0;
			arg1 = parsearg((const char**)&s);
			if(*s)
			{
				if(*s != ',')
					crap("expected ',', got '%c'", *s);
				++s;
				arg2 = parsearg((const char**)&s);
			}
		}

		if(!opn)
		{
			crap("no operator in %s", line);
		}

		o = getop(opn);
		if(!o)
		{
			crap("cannot handle op %s", opn);
		}
		o->func(opn, arg1, arg2, o->data);
		if(assembler_pass)
			debug("   - %s%s", cur_line, cur_line[strlen(cur_line)-1]=='\n'?"":"\n");
	}
}

#ifdef SA_STANDALONE
int main(int argc, char* argv[])
{
	char line[4096];
	size_t len;
	int pass;
	FILE* file = NULL;

	if(argc < 2)
	{
		crap("specify file");
	}

	file = fopen(argv[1], "r");
	if(!file)
	{
		crap("can't open file");
	}

	if(argc > 2)
	{
		fout = fopen(argv[2], "w");
		if(!fout)
		{
			crap("can't open %s for writing", argv[2]);
		}
	}

	for(pass = 0; pass < 2; ++pass)
	{
		if(fseek(file, 0, SEEK_SET))
			crap("can't rewind file");

		if(pass)
		{
			char* b = malloc(assembler_get_code_size());
			if(!b)
				crap("cannot allocate memory");
			assembler_set_output(b);
		}

		assembler_init(pass);

		while(fgets(line, sizeof(line), file))
		{
			len = strlen(line);
			if(!len) continue;

			assemble_line(line, len);
		}
	}

	assembler_init(0);

	fclose(file);

	return 0;
}
#endif
