/* $Id: test-i386-noinline.c,v 1.2 2009/01/27 15:58:54 potyra Exp $ 
 *
 * Copyright (C) 2008-2009 FAUcc Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */
#ifndef __CHIP_H__
#define __CHIP_H__

#include <inttypes.h>

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

typedef enum {
	false,
	true
} bool;

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

/* QEMU: arch_gen_cpu_x86_sim_fast.c */
#define lock()
#define unlock()

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

extern bool bit_get(uint32_t *bitset, int index);
extern void bit_set(uint32_t *bitset, int index, bool value);
extern void bit_set_undef(uint32_t *bitset, int index);

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

extern void __attribute__((__noreturn__)) globalreturn(void);

/* only use at points never reached! */
#define ERROR() error(__FILE__, __LINE__)
extern __attribute__((__noreturn__)) void error(const char *file, int line);

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

#endif /* __CHIP_H__ */

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

extern uint8_t fetch_uint8(uint32_t);
extern uint16_t fetch_uint16(uint32_t);
extern uint32_t fetch_uint32(uint32_t);

extern uint8_t load_uint8(uint32_t addr);
extern uint16_t load_uint16(uint32_t addr);
extern uint32_t load_uint32(uint32_t addr);

extern void store_uint8(uint8_t value, uint32_t addr);
extern void store_uint16(uint16_t value, uint32_t addr);
extern void store_uint32(uint32_t value, uint32_t addr);

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

enum operand_t {
	OPERAND_EAX = 0,
	OPERAND_ECX = 1,
	OPERAND_EDX = 2,
	OPERAND_EBX = 3,
	OPERAND_ESP = 4,
	OPERAND_EBP = 5,
	OPERAND_ESI = 6,
	OPERAND_EDI = 7,
	OPERAND_IMMEDIATE,
	OPERAND_ADDRESS,
	OPERAND_CS,
	OPERAND_SS,
	OPERAND_DS,
	OPERAND_ES,
	OPERAND_FS,
	OPERAND_GS
};

enum size_t {
	SIZE_8,
	SIZE_16,
	SIZE_32
};

enum segment_t {
	SEGMENT_NONE = -1,
	SEGMENT_ES = 0,
	SEGMENT_CS = 1,
	SEGMENT_SS = 2,
	SEGMENT_DS = 3,
	SEGMENT_FS = 4,
	SEGMENT_GS = 5,
	SEGMENT_INVALID = 6
};

enum lock_repeat_t {
	LR_NONE,
	LR_LOCK,
	LR_REPNZ,
	LR_REPZ
};

enum eflag_t {
	EFLAG_CF    =  0,	// Carry Flag
				// 1: Reserved (set to 1)
	EFLAG_PF    =  2,	// Parity Flag
				// 3: Reserved (set to 0)
	EFLAG_AF    =  4,	// Auxiliary Carry Flag
				// 5: Reserved (set to 0)
	EFLAG_ZF    =  6,	// Zero Flag
	EFLAG_SF    =  7,	// Sign Flag
	EFLAG_TF    =  8,	// Trap Flag
	EFLAG_IF    =  9,	// Interrupt Enable Flag
	EFLAG_DF    = 10,	// Direction Flag
	EFLAG_OF    = 11,	// Overflow Flag
	EFLAG_IOPL0 = 12,	// I/O Privilege Level
	EFLAG_IOPL1 = 13,	// I/O Privilege Level
	EFLAG_NT    = 14,	// Nested Task
				// 15: Reserved (set to 0)
	EFLAG_RF    = 16,	// Resume Flag
	EFLAG_VM    = 17,	// Virtual-8086 Mode
	EFLAG_AC    = 18,	// Alignment Check
	EFLAG_VIF   = 19,	// Virtual Interrupt Flag
	EFLAG_VIP   = 20,	// Virtual Interrupt Pending
	EFLAG_ID    = 21	// ID Flag
				// 22: Reserved (set to 0)
				// ...
				// 31: Reserved (set to 0)
};

enum cr0_t {
	CR0_PE = 0,	// Protection Enable
	CR0_MP = 1,	// Monitor Coprocessor
	CR0_EM = 2,	// Emulation
	CR0_TS = 3,	// Task Switched
	CR0_ET = 4,	// Extension Type
	CR0_NE = 5,	// Numeric Error
			// Reserved
	CR0_WP = 16,	// Write Protect
			// Reserved
	CR0_AM = 18,	// Alignment Mask
			// Reserved
	CR0_NW = 29,	// Not Write-through
	CR0_CD = 30,	// Cache Disbale
	CR0_PG = 31	// Paging
};

enum cr3_t {
			// Reserved
	CR3_PWT = 3,	// Page-level Writes Transparent
	CR3_PCD = 4,	// Page-level Cache Disable
			// Reserved
			// 12-31: Page-Directory Base
};

enum cr4_t {
	CR4_VME = 0,	// Virtual-8086 Mode Extensions
	CR4_PVI = 1,	// Protected-Mode Virtual Interrupts
	CR4_TSD = 2,	// Time Stamp Disable
	CR4_DE  = 3,	// Debugging Extensions
	CR4_PSE = 4,	// Page Size Extensiion
	CR4_PAE = 5,	// Physical Address Extension
	CR4_MCE = 6,	// Machine-Check Enable
	CR4_PGE = 7,	// Page Global Enable
	CR4_PCE = 8,	// Performance-Monitoring Counter Enable
	CR4_OSFXSR = 9, // Operating System FXSAVE/FXRSTOR Support
	CR4_OSXMMEXCPT = 10, // Operating System Unmasked Exception Support
			// 11: Reserved (set to 0)
			// ...
			// 31: Reserved (set to 0)
};

enum exception_t {
	EXCEPTION_NONE = -1,
	EXCEPTION_DE  =   0,	// Fault:	Divide Error
	EXCEPTION_DB  =   1,	// Fault/Trap:	Debug
	EXCEPTION_NMI =   2,	// Interrupt:	NMI Interrupt
	EXCEPTION_BP  =   3,	// Trap:	Breakpoint
	EXCEPTION_OF  =   4,	// Trap:	Overflow
	EXCEPTION_BR  =   5,	// Fault:	BOUND Range Exceeded
	EXCEPTION_UD  =   6,	// Fault:	Invalid Opcode (Undefined Opcode)
	EXCEPTION_NM  =   7,	// Fault:	Device Not Available (No Math Coprocessor)
	EXCEPTION_DF  =   8,	// Abort:	Double Fault
	EXCEPTION_009 =   9,	// Fault:	Coprocessor Segment Overrun (reserved)
	EXCEPTION_TS  =  10,	// Fault:	Invalid TSS
	EXCEPTION_NP  =  11,	// Fault:	Segment Not Present
	EXCEPTION_SS  =  12,	// Fault:	Stack-Segment Fault
	EXCEPTIOB_GP  =  13,	// Fault:	General Protection
	EXCEPTION_PF  =  14,	// Fault:	Page Fault
	EXCEPTION_015 =  15,	//		Intel reserved. Do not use.
	EXCEPTION_MF  =  16,	// Fault:	Floating-Point Error (Math Fault)
	EXCEPTION_AC  =  17,	// Fault:	Alignment Check
	EXCEPTION_MC  =  18,	// Abort:	Machine Check
	EXCEPTION_XF  =  19,	// Fault:	Streaming SIMD Extensions
	EXCEPTION_020 =  20,	//		Intel reserved. Do not use.
	/* ... */
	EXCEPTION_031 =  31,	//		Intel reserved, Do not use.
	EXCEPTION_032 =  32,	// Interrupt:	User Defined (Nonreserved) Interrupts
	/* ... */
	EXCEPTION_255 = 255	// Interrupt:	User Defined (Nonreserved) Interrupts
};

enum jcc_t {
	JCC_O  = 0,	// overflow (OF=1)
	JCC_B  = 1,	// below (CF=1)
	JCC_Z  = 2,	// zero (ZF=1)
	JCC_BE = 3,	// below or equal (CF=1 or ZF=1)
	JCC_S  = 4,	// sign (SF=1)
	JCC_P  = 5,	// parity (PF=1)
	JCC_L  = 6,	// less (SF<>OF)
	JCC_LE = 7,	// less or equal (ZF=1 or SF<>OF)
};

/******************************************************************************
 *
 * General-purpose data registers
 *
 ******************************************************************************/

static uint32_t eax; // Accumulator for operands and results data
static uint32_t ecx; // Counter for string and loop operations
static uint32_t edx; // I/O pointer
static uint32_t ebx; // Pointer to data in the DS segment
static uint32_t esp; // Stack pointer (in the SS segment)
static uint32_t ebp; // Pointer to data on the stack (in the SS segment)
static uint32_t esi; // Pointer to data in the segment pointed to by the DS register; source pointer for string operations
static uint32_t edi; // Pointer to data (or destination) in the segment pointed to by the ES register; destination pointer for string operations

/******************************************************************************
 *
 * Segment Registers
 *
 ******************************************************************************/

static uint16_t cs; // Code Segment
static uint16_t ss; // Stack Segment
static uint16_t ds; // Data Segment
static uint16_t es; // Data Segment
static uint16_t fs; // Data Segment
static uint16_t gs; // Data Segment

/******************************************************************************
 *
 * Status and Control Registers
 *
 ******************************************************************************/

static uint32_t eip;	// Instruction pointer
static uint32_t eflags; // EFLAGS Register

//static uint32_t cr0; // Control Register 0: Contains system control flags that control operating mode and states of the processor
//static uint32_t cr1; // Control Register 1: Reserved
//static uint32_t cr2; // Control Register 2: Contains the page-fault linear address (the linear address that caused a page fault)
//static uint32_t cr3; // Control Register 3: Contains the physical address of the base of the page directory and two flags (PCD and PWT)
//static uint32_t cr4; // Control Register 4: Contains a group of flags that enable several architecture extensions, as well as indicating the level of OS support for the Streaming SIMD Extension

/******************************************************************************
 *
 * Memory-management Registers
 *
 ******************************************************************************/

//static uint32_t gdtr_base_address;
//static uint16_t gdtr_table_limit;

//static uint32_t idtr_base_address;
//static uint16_t idtr_table_limit;

//static uint16_t tr_segment_selector;
//static uint32_t tr_base_address;
//static uint16_t tr_segment_limit;

//static uint16_t ldtr_segment_selector;
//static uint32_t ldtr_base_address;
//static uint16_t ldtr_segment_limit;

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

static bool cs_size32; // D Flag in Code Segment Descriptor
static bool ss_size32; // address size attribute of the stack segment (QEMU: ss32)

static bool prefix_operand_size_override;
static bool prefix_address_size_override;

static enum segment_t prefix_segment_override;
static enum lock_repeat_t prefix_lock_repeat;

static enum exception_t exception_vector;
static uint16_t exception_error_code;

/******************************************************************************
 *
 * Mask
 *
 ******************************************************************************/

static uint32_t mask(uint32_t t, enum size_t size) {
	uint32_t value;

	switch (size) {
		case SIZE_8:
			value = t & 0xff;
			break;
		case SIZE_16:
			value = t & 0xffff;
			break;
		case SIZE_32:
			value = t & 0xffffffff;
			break;
		default:
			ERROR();
	}
	return value;
}

/******************************************************************************
 *
 * Exception
 *
 ******************************************************************************/

static void exception(enum exception_t vector, uint16_t error_code) {
	exception_vector = vector;
	exception_error_code = error_code;
	globalreturn();
}

/******************************************************************************
 *
 * EFLAGS Register Accessors
 *
 ******************************************************************************/

static bool eflag_get(enum eflag_t eflag) {
	return bit_get(&eflags, eflag);
}

static void eflag_set(enum eflag_t eflag, bool value) {
	bit_set(&eflags, eflag, value);
}

static void eflag_set_undef(enum eflag_t eflag) {
	bit_set_undef(&eflags, eflag);
}

/******************************************************************************
 *
 * Effective Operand- and Address-Size Attributes
 *
 * D Flag in Code Segment Descriptor	 0   0   0   0   1   1   1   1
 * Operand-Size Prefix 0x66		 N   N   Y   Y   N   N   Y   Y
 * Address-Size Prefix 0x67		 N   Y   N   Y   N   Y   N   Y
 * Effective Operand Size		16  16  32  32  32  32  16  16
 * Effective Address Size		16  32  16  32  32  16  32  16
 *
 ******************************************************************************/

static enum size_t effective_operand_size(void) {
	if (cs_size32 ^ prefix_operand_size_override)
		return SIZE_32;
	else
		return SIZE_16;
}

static enum size_t effective_address_size(void) {
	if (cs_size32 ^ prefix_address_size_override)
		return SIZE_32;
	else
		return SIZE_16;
}

/******************************************************************************
 *
 * Instruction Fetch
 *
 ******************************************************************************/

static uint32_t fetch(uint32_t *counter, enum size_t size) {
	uint32_t value;

	if (!counter)
		ERROR();

	switch (size) {
		case SIZE_8:
			value = fetch_uint8(eip + cs);
			*counter += 1;
			break;
		case SIZE_16:
			value = fetch_uint16(eip + cs);
			*counter += 2;
			break;
		case SIZE_32:
			value = fetch_uint32(eip + cs);
			*counter += 4;
			break;
		default:
			ERROR();
	}
	return value;
}

/******************************************************************************
 *
 * Store (Register and Memeory)
 *
 *		SIZE_8  SIZE_16  SIZE_32
 * OPERAND_EAX	  AL       AX      EAX
 * OPERAND_ECX    CL       CX      ECX
 * OPERAND_EDX    DL       DX      EDX
 * OPERAND_EBX    BL       BX      EBX
 * OPERAND_ESP    AH       SP      ESP
 * OPERAND_EBP    CH       BP      EBP
 * OPERAND_ESI    DH       SI      ESI
 * OPERAND_EDI    BH       DI      EDI
 *
 ******************************************************************************/

static void store(uint32_t t, enum operand_t operand, enum size_t size, uint32_t addr, enum segment_t segment) {
	switch (operand) {
		case OPERAND_EAX:
			eax = mask(t, size);
			break;
		case OPERAND_ECX:
			ecx = mask(t, size);
			break;
		case OPERAND_EDX:
			edx = mask(t, size);
			break;
		case OPERAND_EBX:
			ebx = mask(t, size);
			break;
		case OPERAND_ESP:
			if (size == SIZE_8) // AH !!!
				eax = (t & 0xff) << 8;
			else
				esp = mask(t, size);
			break;
		case OPERAND_EBP:
			if (size == SIZE_8) // CH !!!
				ecx = (t & 0xff) << 8;
			else
				ebp = mask(t, size);
			break;
		case OPERAND_ESI:
			if (size == SIZE_8) // DH !!!
				edx = (t & 0xff) << 8;
			else
				esi = mask(t, size);
			break;
		case OPERAND_EDI:
			if (size == SIZE_8) // BH !!!
				ebx = (t & 0xff) << 8;
			else
				edi = mask(t, size);
			break;
		case OPERAND_IMMEDIATE:
			ERROR();
			break;
		case OPERAND_ADDRESS:
			switch (segment) {
				case SEGMENT_NONE:
					break;
				case SEGMENT_CS:
					addr += cs;
					break;
				case SEGMENT_SS:
					addr += ss;
					break;
				case SEGMENT_DS:
					addr += ds;
					break;
				case SEGMENT_ES:
					addr += es;
					break;
				case SEGMENT_FS:
					addr += fs;
					break;
				case SEGMENT_GS:
					addr += gs;
					break;
				default:
					ERROR();
			}
			switch (size) {
				case SIZE_8:
					store_uint8(t, addr);
					break;
				case SIZE_16:
					store_uint16(t, addr);
					break;
				case SIZE_32:
					store_uint32(t, addr);
					break;
				default:
					ERROR();
			}
			break;
		case OPERAND_CS:
			cs = mask(t, size);
			break;
		case OPERAND_SS:
			ss = mask(t, size);
			break;
		case OPERAND_DS:
			ds = mask(t, size);
			break;
		case OPERAND_ES:
			es = mask(t, size);
			break;
		case OPERAND_FS:
			fs = mask(t, size);
			break;
		case OPERAND_GS:
			gs = mask(t, size);
			break;
		default:
			ERROR();
	}
}

/******************************************************************************
 *
 * Load (Register and Memory)
 *
 *		SIZE_8  SIZE_16  SIZE_32
 * OPERAND_EAX	  AL       AX      EAX
 * OPERAND_ECX    CL       CX      ECX
 * OPERAND_EDX    DL       DX      EDX
 * OPERAND_EBX    BL       BX      EBX
 * OPERAND_ESP    AH       SP      ESP
 * OPERAND_EBP    CH       BP      EBP
 * OPERAND_ESI    DH       SI      ESI
 * OPERAND_EDI    BH       DI      EDI
 *
 ******************************************************************************/

static uint32_t load(uint32_t *counter, enum operand_t operand, enum size_t size, uint32_t addr, enum segment_t segment) {
	uint32_t value;

	switch (operand) {
		case OPERAND_EAX:
			value = eax;
			break;
		case OPERAND_ECX:
			value = ecx;
			break;
		case OPERAND_EDX:
			value = edx;
			break;
		case OPERAND_EBX:
			value = ebx;
			break;
		case OPERAND_ESP:
			if (size == SIZE_8) // AH !!!
				value = eax >> 8;
			else
				value = esp;
			break;
		case OPERAND_EBP:
			if (size == SIZE_8) // CH !!!
				value = ecx >> 8;
			else
				value = ebp;
			break;
		case OPERAND_ESI:
			if (size == SIZE_8) // DH !!!
				value = edx >> 8;
			else
				value = esi;
			break;
		case OPERAND_EDI:
			if (size == SIZE_8) // BH !!!
				value = ebx >> 8;
			else
				value = edi;
			break;
		case OPERAND_IMMEDIATE:
			value = fetch(counter, size);
			break;
		case OPERAND_ADDRESS:
			switch (segment) {
				case SEGMENT_NONE:
					break;
				case SEGMENT_CS:
					addr += cs;
					break;
				case SEGMENT_SS:
					addr += ss;
					break;
				case SEGMENT_DS:
					addr += ds;
					break;
				case SEGMENT_ES:
					addr += es;
					break;
				case SEGMENT_FS:
					addr += fs;
					break;
				case SEGMENT_GS:
					addr += gs;
					break;
				default:
					ERROR();
			}
			switch (size) {
				case SIZE_8:
					value = load_uint8(addr);
					break;
				case SIZE_16:
					value = load_uint16(addr);
					break;
				case SIZE_32:
					value = load_uint32(addr);
					break;
				default:
					ERROR();
			}
			break;
		case OPERAND_CS:
			value = cs;
			break;
		case OPERAND_SS:
			value = ss;
			break;
		case OPERAND_DS:
			value = ds;
			break;
		case OPERAND_ES:
			value = es;
			break;
		case OPERAND_FS:
			value = fs;
			break;
		case OPERAND_GS:
			value = gs;
			break;
		default:
			ERROR();
	}
	return value;
}

/******************************************************************************
 *
 * Update SF - Sign flag
 *
 * Set equal to the most-significant bit of the result, which is the sign bit
 * of a signed integer. (0 indicates a positive value and 1 indicates a negative
 * value.)
 *
 ******************************************************************************/

static void update_sf(uint32_t t, enum size_t size) {
	switch (size) {
		case SIZE_8:
			eflag_set(EFLAG_SF, t >> 7);
			break;
		case SIZE_16:
			eflag_set(EFLAG_SF, t >> 15);
			break;
		case SIZE_32:
			eflag_set(EFLAG_SF, t >> 31);
			break;
		default:
			ERROR();
	}
}

/******************************************************************************
 *
 * Update ZF - Zero flag
 *
 * Set if the result is zero; cleared otherwise.
 *
 ******************************************************************************/

static void update_zf(uint32_t t) {
	eflag_set(EFLAG_ZF, t == 0);
}

/******************************************************************************
 *
 * Update PF - Parity flag
 *
 * Set if the least-significant byte of the result contains an even number
 * of 1 bits; cleared otherwise.
 *
 ******************************************************************************/

static void update_pf(uint32_t t) {
	static const uint8_t parity_table[256] = {
		1, 0, 0, 1, 0, 1, 1, 0,	0, 1, 1, 0, 1, 0, 0, 1, // 0x00 - 0x0f
		0, 1, 1, 0, 1, 0, 0, 1,	1, 0, 0, 1, 0, 1, 1, 0, // 0x10 - 0x1f
		0, 1, 1, 0, 1, 0, 0, 1,	1, 0, 0, 1, 0, 1, 1, 0, // 0x20 - 0x2f
		1, 0, 0, 1, 0, 1, 1, 0,	0, 1, 1, 0, 1, 0, 0, 1, // 0x30 - 0x3f
		0, 1, 1, 0, 1, 0, 0, 1,	1, 0, 0, 1, 0, 1, 1, 0, // 0x40 - 0x4f
		1, 0, 0, 1, 0, 1, 1, 0,	0, 1, 1, 0, 1, 0, 0, 1, // 0x50 - 0x5f
		1, 0, 0, 1, 0, 1, 1, 0,	0, 1, 1, 0, 1, 0, 0, 1, // 0x60 - 0x6f
		0, 1, 1, 0, 1, 0, 0, 1,	1, 0, 0, 1, 0, 1, 1, 0, // 0x70 - 0x7f
		0, 1, 1, 0, 1, 0, 0, 1,	1, 0, 0, 1, 0, 1, 1, 0, // 0x80 - 0x8f
		1, 0, 0, 1, 0, 1, 1, 0,	0, 1, 1, 0, 1, 0, 0, 1, // 0x90 - 0x9f
		1, 0, 0, 1, 0, 1, 1, 0,	0, 1, 1, 0, 1, 0, 0, 1, // 0xa0 - 0xaf
		0, 1, 1, 0, 1, 0, 0, 1,	1, 0, 0, 1, 0, 1, 1, 0, // 0xb0 - 0xbf
		1, 0, 0, 1, 0, 1, 1, 0,	0, 1, 1, 0, 1, 0, 0, 1, // 0xc0 - 0xcf
		0, 1, 1, 0, 1, 0, 0, 1,	1, 0, 0, 1, 0, 1, 1, 0, // 0xd0 - 0xdf
		0, 1, 1, 0, 1, 0, 0, 1,	1, 0, 0, 1, 0, 1, 1, 0, // 0xe0 - 0xef
		1, 0, 0, 1, 0, 1, 1, 0,	0, 1, 1, 0, 1, 0, 0, 1, // 0xf0 - 0xff
	};
	eflag_set(EFLAG_PF, parity_table[t & 0xff]);
}

/******************************************************************************
 *
 * Update 'Result' - Flags
 *
 * Update SF, ZF and PF.
 *
 ******************************************************************************/

static void update_sf_zf_pf(uint32_t t, enum size_t size) {
	update_sf(t, size);
	update_zf(t);
	update_pf(t);
}

/******************************************************************************
 *
 * Arithmetic and Logic Generator Functions
 *
 ******************************************************************************/

/* Increment by 1 */
static void inc(uint32_t counter, enum operand_t operand, enum size_t size, uint32_t addr, enum segment_t segment) {
	uint32_t t0, t1, t2;

	t1 = load(&counter, operand, size, addr, segment);
	t2 = 1;
	t0 = mask(t1 + t2, size);
	store(t0, operand, size, addr, segment);
	update_sf_zf_pf(t0, size);
	/* cf unchanged */
	eflag_set(EFLAG_OF, t0 == 0);
	eflag_set(EFLAG_AF, (t1 ^ t2 ^ t0) & 0x10);
	eip += counter;
}

/* Decrement by 1 */
static void dec(uint32_t counter, enum operand_t operand, enum size_t size, uint32_t addr, enum segment_t segment) {
	uint32_t t0, t1, t2;

	t1 = load(&counter, operand, size, addr, segment);
	t2 = 1;
	t0 = mask(t1 - t2, size);
	store(t0, operand, size, addr, segment);
	update_sf_zf_pf(t0, size);
	/* cf unchanged */
	eflag_set(EFLAG_OF, t0 == mask(-1, size));
	eflag_set(EFLAG_AF, (t1 ^ t2 ^ t0) & 0x10);
	eip += counter;
}

/* Add */
static void add(uint32_t counter, enum operand_t operand_a, enum size_t size_a, enum operand_t operand_b, enum size_t size_b, uint32_t addr, enum segment_t segment) {
	uint32_t t0, t1, t2;

	t1 = load(&counter, operand_a, size_a, addr, segment);
	t2 = load(&counter, operand_b, size_b, addr, segment);
	t0 = mask(t1 + t2, size_a);
	store(t0, operand_a, size_a, addr, segment);
	update_sf_zf_pf(t0, size_a);
	eflag_set(EFLAG_CF, t0 < t1);
	eflag_set(EFLAG_OF, (t1 ^ t2 ^ -1) & (t1 ^ t0));
	eflag_set(EFLAG_AF, (t1 ^ t2 ^ t0) & 0x10);
	eip += counter;
}

/* Logical Inclusive OR */
static void or(uint32_t counter, enum operand_t operand_a, enum size_t size_a, enum operand_t operand_b, enum size_t size_b, uint32_t addr, enum segment_t segment) {
	uint32_t t0, t1, t2;

	t1 = load(&counter, operand_a, size_a, addr, segment);
	t2 = load(&counter, operand_b, size_b, addr, segment);
	t0 = mask(t1 | t2, size_a);
	store(t0, operand_a, size_a, addr, segment);
	update_sf_zf_pf(t0, size_a);
	eflag_set(EFLAG_CF, false);
	eflag_set(EFLAG_OF, false);
	eflag_set_undef(EFLAG_AF);
	eip += counter;
}

/* Add with Carry */
static void adc(uint32_t counter, enum operand_t operand_a, enum size_t size_a, enum operand_t operand_b, enum size_t size_b, uint32_t addr, enum segment_t segment) {
	uint32_t t0, t1, t2;

	t1 = load(&counter, operand_a, size_a, addr, segment);
	t2 = load(&counter, operand_b, size_b, addr, segment);
	t0 = mask(t1 + t2 + eflag_get(EFLAG_CF), size_a);
	store(t0, operand_a, size_a, addr, segment);
	update_sf_zf_pf(t0, size_a);
	eflag_set(EFLAG_CF, t0 <= t1);
	eflag_set(EFLAG_OF, (t1 ^ t2 ^ -1) & (t1 ^ t0));
	eflag_set(EFLAG_AF, (t1 ^ t2 ^ t0) & 0x10);
	eip += counter;
}

/* Integer Subtraction with Borrow */
static void sbb(uint32_t counter, enum operand_t operand_a, enum size_t size_a, enum operand_t operand_b, enum size_t size_b, uint32_t addr, enum segment_t segment) {
	uint32_t t0, t1, t2;

	t1 = load(&counter, operand_a, size_a, addr, segment);
	t2 = load(&counter, operand_b, size_b, addr, segment);
	t0 = mask(t1 - (t2 + eflag_get(EFLAG_CF)), size_a);
	store(t0, operand_a, size_a, addr, segment);
	update_sf_zf_pf(t0, size_a);
	eflag_set(EFLAG_CF, t1 <= t2);
	eflag_set(EFLAG_OF, (t1 ^ t2) & (t1 ^ t0));
	eflag_set(EFLAG_AF, (t1 ^ t2 ^ t0) & 0x10);
	eip += counter;
}

/* Logical AND */
static void and(uint32_t counter, enum operand_t operand_a, enum size_t size_a, enum operand_t operand_b, enum size_t size_b, uint32_t addr, enum segment_t segment) {
	uint32_t t0, t1, t2;

	t1 = load(&counter, operand_a, size_a, addr, segment);
	t2 = load(&counter, operand_b, size_b, addr, segment);
	t0 = mask(t1 & t2, size_a);
	store(t0, operand_a, size_a, addr, segment);
	update_sf_zf_pf(t0, size_a);
	eflag_set(EFLAG_CF, false);
	eflag_set(EFLAG_OF, false);
	eflag_set_undef(EFLAG_AF);
	eip += counter;
}

/* Substract */
static void sub(uint32_t counter, enum operand_t operand_a, enum size_t size_a, enum operand_t operand_b, enum size_t size_b, uint32_t addr, enum segment_t segment) {
	uint32_t t0, t1, t2;

	t1 = load(&counter, operand_a, size_a, addr, segment);
	t2 = load(&counter, operand_b, size_b, addr, segment);
	t0 = mask(t1 - t2, size_a);
	store(t0, operand_a, size_a, addr, segment);
	update_sf_zf_pf(t0, size_a);
	eflag_set(EFLAG_CF, t1 < t2);
	eflag_set(EFLAG_OF, (t1 ^ t2) & (t1 ^ t0));
	eflag_set(EFLAG_AF, (t1 ^ t2 ^ t0) & 0x10);
	eip += counter;
}

/* Logical Exclusive OR */
static void xor(uint32_t counter, enum operand_t operand_a, enum size_t size_a, enum operand_t operand_b, enum size_t size_b, uint32_t addr, enum segment_t segment) {
	uint32_t t0, t1, t2;

	t1 = load(&counter, operand_a, size_a, addr, segment);
	t2 = load(&counter, operand_b, size_b, addr, segment);
	t0 = mask(t1 ^ t2, size_a);
	store(t0, operand_a, size_a, addr, segment);
	update_sf_zf_pf(t0, size_a);
	eflag_set(EFLAG_CF, false);
	eflag_set(EFLAG_OF, false);
	eflag_set_undef(EFLAG_AF);
	eip += counter;
}

/* Compare Two Operands */
static void cmp(uint32_t counter, enum operand_t operand_a, enum size_t size_a, enum operand_t operand_b, enum size_t size_b, uint32_t addr, enum segment_t segment) {
	uint32_t t0, t1, t2;

	t1 = load(&counter, operand_a, size_a, addr, segment);
	t2 = load(&counter, operand_b, size_b, addr, segment);
	t0 = mask(t1 - t2, size_a);
	store(t0, operand_a, size_a, addr, segment);
	update_sf_zf_pf(t0, size_a);
	eflag_set_undef(EFLAG_CF); // FIXME
	eflag_set_undef(EFLAG_OF); // FIXME
	eflag_set_undef(EFLAG_AF); // FIXME
	eip += counter;
}

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

static void test(uint32_t counter, enum operand_t operand_a, enum size_t size_a, enum operand_t operand_b, enum size_t size_b, uint32_t addr, enum segment_t segment) {
	// TODO
	eip += counter;
}

static void not(uint32_t counter, enum operand_t operand, enum size_t size, uint32_t addr, enum segment_t segment) {
	// TODO
	eip += counter;
}

static void neg(uint32_t counter, enum operand_t operand, enum size_t size, uint32_t addr, enum segment_t segment) {
	// TODO
	eip += counter;
}

static void mul(uint32_t counter, enum operand_t operand_a, enum size_t size_a, enum operand_t operand_b, enum size_t size_b, uint32_t addr, enum segment_t segment) {
	// TODO
	eip += counter;
}

static void imul(uint32_t counter, enum operand_t operand_a, enum size_t size_a, enum operand_t operand_b, enum size_t size_b, uint32_t addr, enum segment_t segment) {
	// TODO
	eip += counter;
}

static void div(uint32_t counter, enum operand_t operand_a, enum size_t size_a, enum operand_t operand_b, enum size_t size_b, uint32_t addr, enum segment_t segment) {
	// TODO
	eip += counter;
}

static void idiv(uint32_t counter, enum operand_t operand_a, enum size_t size_a, enum operand_t operand_b, enum size_t size_b, uint32_t addr, enum segment_t segment) {
	// TODO
	eip += counter;
}

/******************************************************************************
 *
 * MOV
 *
 ******************************************************************************/

static void mov(uint32_t counter, enum operand_t operand_dst, enum size_t size_dst, enum operand_t operand_src, enum size_t size_src, uint32_t addr, enum segment_t segment) {
	uint32_t t;

	t = load(&counter, operand_src, size_src, addr, segment);
	store(t, operand_dst, size_dst, addr, segment);
	eip += counter;
}

/******************************************************************************
 *
 * Push and Pop Generator Functions
 *
 ******************************************************************************/

static void push(uint32_t counter, enum operand_t operand, enum size_t operand_size, uint32_t addr, enum segment_t segment, enum size_t stack_size) {
	uint32_t a, t;

	switch (operand_size) {
		case SIZE_8: ERROR();
		case SIZE_16: esp -= 2; break;
		case SIZE_32: esp -= 4; break;
		default: ERROR();
	}

	esp = mask(esp, stack_size);

	a = mask(esp + ss, stack_size);

	t = load(&counter, operand, operand_size, addr, segment);

	switch (operand_size) {
		case SIZE_8: ERROR();
		case SIZE_16: store_uint16(t, a); break;
		case SIZE_32: store_uint32(t, a); break;
		default: ERROR();
	}

	eip += counter;
}

static void pop(uint32_t counter, enum operand_t operand, enum size_t operand_size, uint32_t addr, enum segment_t segment, enum size_t stack_size) {
	uint32_t a, t;

	a = mask(esp + ss, stack_size);

	switch (operand_size) {
		case SIZE_8: ERROR();
		case SIZE_16: t = load_uint16(a); break;
		case SIZE_32: t = load_uint32(a); break;
		default: ERROR();
	}

	store(t, operand, operand_size, addr, segment);

	switch (operand_size) {
		case SIZE_8: ERROR();
		case SIZE_16: esp += 2; break;
		case SIZE_32: esp += 4; break;
		default: ERROR();
	}

	esp = mask(esp, stack_size);

	eip += counter;
}

static void jecxz(uint32_t counter, enum size_t size) {
	int8_t offset;
	bool condition;

	offset = fetch(&counter, SIZE_8);

	condition = mask(ecx, size) == 0;

	eip += counter;

	// FIXME: check segment limits

	if (condition) {
		eip += offset;
		eip = mask(eip, size);
	}
}

static void jcc(uint32_t counter, enum jcc_t cc, bool negate, enum size_t size) {
	int32_t offset;
	bool condition;

	offset = fetch(&counter, size);

	switch (cc) {
		case JCC_O:
			condition = eflag_get(EFLAG_OF);
			break;
		case JCC_B:
			condition = eflag_get(EFLAG_CF);
			break;
		case JCC_Z:
			condition = eflag_get(EFLAG_ZF);
			break;
		case JCC_BE:
			condition = eflag_get(EFLAG_CF) || eflag_get(EFLAG_ZF);
			break;
		case JCC_S:
			condition = eflag_get(EFLAG_SF);
			break;
		case JCC_P:
			condition = eflag_get(EFLAG_PF);
			break;
		case JCC_L:
			condition = eflag_get(EFLAG_SF) != eflag_get(EFLAG_OF);
			break;
		case JCC_LE:
			condition = eflag_get(EFLAG_ZF) || (eflag_get(EFLAG_SF) != eflag_get(EFLAG_OF));
			break;
		default:
			ERROR();
	}

	condition ^= negate;

	eip += counter;

	// FIXME: check segment limits

	if (condition) {
		eip += offset;

		if (size == SIZE_16)
			eip &= 0xffff;
	}
}


/******************************************************************************
 *
 * LAHF - Load Status Flags into AH Register
 *
 * AH = SF:ZF:0:AF:0:PF:1:CF
 *
 * Flags:	None
 * Exceptions:	None
 *
 ******************************************************************************/

static void lahf(uint32_t counter) {
	uint8_t ah;

	ah = 0;
	ah &= eflag_get(EFLAG_CF) << EFLAG_CF;
	ah &= 1 << 1;
	ah &= eflag_get(EFLAG_PF) << EFLAG_PF;
	ah &= 0 << 3;
	ah &= eflag_get(EFLAG_AF) << EFLAG_AF;
	ah &= 0 << 5;
	ah &= eflag_get(EFLAG_ZF) << EFLAG_ZF;
	ah &= eflag_get(EFLAG_SF) << EFLAG_SF;

	eax = ah << 8;

	eip += counter;
}

/******************************************************************************
 *
 * DAA - Decimal Adjust AL after Addition
 *
 * Flags:	CF, AF, SF, ZF, PF, OF
 * Exceptions:	None
 *
 ******************************************************************************/

static void daa(uint32_t counter) {
	uint8_t al;
	bool af, cf;

	al = eax & 0xff;
	af = eflag_get(EFLAG_AF);
	cf = eflag_get(EFLAG_CF);

	if (((al & 0x0f) > 9) || af) {
		cf = ((al > 0xf9) || cf);
		af = true;
		al += 6;
	} else {
		af = false;
	}

	if (((al & 0xf0) > 0x90) || cf) {
		al += 0x60;
		cf = true;
	} else {
		cf = false;
	}

	eax = al;
	eflag_set(EFLAG_AF, af);
	eflag_set(EFLAG_CF, cf);
	update_sf_zf_pf(al, SIZE_8);

	eip += counter;
}

/******************************************************************************
 *
 * Handler for the operand-identifier byte (ModR/M byte) and the
 * addressing-mode specifier byte (SIB byte).
 *
 * Reads the ModR/M byte and if required: 1 SIB byte and 1, 2 or 4 bytes
 * displacement.
 *
 * Possible return values for operand_reg:
 *	OPERAND_EAX, OPERAND_ECX, OPERAND_EDX, OPERAND_EBX,
 *	OPERAND_ESP, OPERAND_EBP, OPERAND_ESI, OPERAND_EDI
 *
 * Possible return values for operand_rm:
 *	OPERAND_EAX, OPERAND_ECX, OPERAND_EDX, OPERAND_EBX,
 *	OPERAND_ESP, OPERAND_EBP, OPERAND_ESI, OPERAND_EDI,
 *	OPERAND_ADDRESS (the address value is stored in addr)
 *
 * Mod/RM:
 *
 *     7 6   5 4 3   2 1 0
 *     Mod    Reg     R/M
 *
 * SIB:
 *     7 6   5 4 3   2 1 0
 *    Scale  Index   Base
 *
 ******************************************************************************/

static void fetch_mod_rm(uint32_t *counter, enum operand_t *operand_reg, enum operand_t *operand_rm, uint32_t *addr, enum segment_t *segment) {
	int mod_rm, mod, reg, rm;

	mod_rm = fetch(counter, SIZE_8);

	mod = (mod_rm >> 6) & 3;
	reg = (mod_rm >> 3) & 7;
	rm = mod_rm & 7;

	if (operand_reg)
		*operand_reg = reg;

	if (!operand_rm)
		return;

	if (mod == 3) {
		*operand_rm = rm;
		return;
	}

	*operand_rm = OPERAND_ADDRESS;

	if (!addr)
		ERROR();
	if (!segment)
		ERROR();

	// now calculate the address
	if (cs_size32 ^ prefix_address_size_override) {
		int scale, index, base;
		uint32_t disp;

		if (rm == 4) {
			int sib = fetch(counter, SIZE_8);
			scale = (sib >> 6) & 3;
			index = (sib >> 3) & 7;
			base = sib & 7;
		} else {
			scale = 0;
			index = 0;
			base = rm;
		}

		switch (mod & 3) {
			case 0: // no disp, exception: rm == 5
				if (base == 5) {
					disp = fetch(counter, SIZE_32);
					base = -1;
				} else {
					disp = 0;
				}
				break;
			case 1: // disp8
				disp = fetch(counter, SIZE_8);
				break;
			case 2: // disp32
				disp = fetch(counter, SIZE_32);
				break;
			case 3: // register only: already handled
			default:
				ERROR();
		}

		if (disp != 0)
			*addr = disp; // IMMEDIATE
		else
			*addr = 0;

		if (base >= 0) {
			// TODO? popl handling with esp
			switch (base & 7) {
				case OPERAND_EAX: *addr += eax; break;
				case OPERAND_ECX: *addr += ecx; break;
				case OPERAND_EDX: *addr += edx; break;
				case OPERAND_EBX: *addr += ebx; break;
				case OPERAND_ESP: *addr += esp; break;
				case OPERAND_EBP: *addr += ebp; break;
				case OPERAND_ESI: *addr += esi; break;
				case OPERAND_EDI: *addr += edi; break;
				default: ERROR();
			}
		}

		switch (index & 7) {
			case OPERAND_EAX: *addr += eax * (1 << scale); break;
			case OPERAND_ECX: *addr += ecx * (1 << scale); break;
			case OPERAND_EDX: *addr += edx * (1 << scale); break;
			case OPERAND_EBX: *addr += ebx * (1 << scale); break;
			case OPERAND_ESP: ERROR();
			case OPERAND_EBP: *addr += ebp * (1 << scale); break;
			case OPERAND_ESI: *addr += esi * (1 << scale); break;
			case OPERAND_EDI: *addr += edi * (1 << scale); break;
			default: ERROR();
		}

		if (prefix_segment_override != SEGMENT_NONE)
			*segment = prefix_segment_override;
		else if (base == OPERAND_EBP || base == OPERAND_ESP)
			*segment = SEGMENT_SS;
		else
			*segment = SEGMENT_DS;
	} else {
		uint32_t disp;

		switch (mod) {
			case 0: // no disp, exception: rm == 6
				if (rm == 6) {
					disp = fetch(counter, SIZE_16);
					rm = -1;
				} else {
					disp = 0;
				}
				break;
			case 1: // disp8
				disp = fetch(counter, SIZE_8);
				break;
			case 2: // disp16
				disp = fetch(counter, SIZE_16);
				break;
			case 3: // register only (already checked)
			default:
				ERROR();
		}

		if (disp != 0)
			*addr = disp; // IMMEDIATE
		else
			*addr = 0;

		switch (rm) {
			case -1: break;
			case 0: *addr += ebx + esi; break;
			case 1: *addr += ebx + edi; break;
			case 2: *addr += ebp + esi; break;
			case 3: *addr += ebp + edi; break;
			case 4: *addr += esi; break;
			case 5: *addr += edi; break;
			case 6: *addr += ebp; break;
			case 7: *addr += ebx; break;
			default: ERROR();
		}

		*addr = *addr & 0xffff;

		if (prefix_segment_override != SEGMENT_NONE)
			*segment = prefix_segment_override;
		else if (rm == 2 || rm == 3 || rm == 6)
			*segment = SEGMENT_SS;
		else
			*segment = SEGMENT_DS;
	}
}

/******************************************************************************
 *
 * Handler for arithmetic and logic instructions
 *
 * Opcode:
 *
 *     7 6    5 4 3       2           1           0
 *     0 0  Operation  Direction  Immediate  Operandsize
 *
 ******************************************************************************/

static void handle_arithmetic_and_logic(uint32_t counter, uint32_t opcode) {
	int operation, direction, immediate;
	enum operand_t operand_a, operand_b;
	enum segment_t segment;
	enum size_t size;
	uint32_t addr;

	operation = (opcode >> 3) & 7;
	direction = (opcode >> 1) & 1;
	immediate = (opcode >> 2) & 1;

	if ((opcode & 1) == 0)
		size = SIZE_8;
	else
		size = effective_operand_size();

	if (immediate == 1) {
		operand_a = OPERAND_EAX;
		operand_b = OPERAND_IMMEDIATE;
	} else {
		enum operand_t operand_reg, operand_rm;

		fetch_mod_rm(&counter, &operand_reg, &operand_rm, &addr, &segment);

		if (direction == 0) {
			operand_a = operand_rm;
			operand_b = operand_reg;
		} else {
			operand_a = operand_reg;
			operand_b = operand_rm;
		}
	}

	switch (operation & 7) {
		case 0x0: add(counter, operand_a, size, operand_b, size, addr, segment); break;
		case 0x1: or(counter, operand_a, size, operand_b, size, addr, segment); break;
		case 0x2: adc(counter, operand_a, size, operand_b, size, addr, segment); break;
		case 0x3: sbb(counter, operand_a, size, operand_b, size, addr, segment); break;
		case 0x4: and(counter, operand_a, size, operand_b, size, addr, segment); break;
		case 0x5: sub(counter, operand_a, size, operand_b, size, addr, segment); break;
		case 0x6: xor(counter, operand_a, size, operand_b, size, addr, segment); break;
		case 0x7: cmp(counter, operand_a, size, operand_b, size, addr, segment); break;
		default: ERROR();
	}
}

/******************************************************************************
 *
 * Handler for Immediate Group 1
 *
 * Bits 5, 4 and 3 of ModR/M byte used as an opcode extension.
 *
 ******************************************************************************/

static void handle_group_1(uint32_t counter, uint32_t opcode) {
	enum operand_t operand_a, operand_b;
	enum size_t size_a, size_b;
	enum segment_t segment;
	int operation;
	uint32_t addr;

	if ((opcode & 1) == 0)
		size_a = SIZE_8;
	else
		size_a = effective_operand_size();

	if (opcode == 0x83)
		size_b = SIZE_8;
	else
		size_b = size_a;

	operand_b = OPERAND_IMMEDIATE;

	fetch_mod_rm(&counter, (enum operand_t *) &operation, &operand_a, &addr, &segment);

	switch (operation & 7) {
		case 0x0: add(counter, operand_a, size_a, operand_b, size_b, addr, segment); break;
		case 0x1: or(counter, operand_a, size_a, operand_b, size_b, addr, segment); break;
		case 0x2: adc(counter, operand_a, size_a, operand_b, size_b, addr, segment); break;
		case 0x3: sbb(counter, operand_a, size_a, operand_b, size_b, addr, segment); break;
		case 0x4: and(counter, operand_a, size_a, operand_b, size_b, addr, segment); break;
		case 0x5: sub(counter, operand_a, size_a, operand_b, size_b, addr, segment); break;
		case 0x6: xor(counter, operand_a, size_a, operand_b, size_b, addr, segment); break;
		case 0x7: cmp(counter, operand_a, size_a, operand_b, size_b, addr, segment); break;
		default: ERROR();
	}
}

/******************************************************************************
 *
 * Handler for Unary Group 3
 *
 * Bits 5, 4 and 3 of ModR/M byte used as an opcode extension.
 *
 ******************************************************************************/

static void handle_group_3(uint32_t counter, uint32_t opcode) {
	enum operand_t operand_a, operand_b;
	enum segment_t segment;
	enum size_t size;
	int operation;
	uint32_t addr;

	if ((opcode & 1) == 0)
		size = SIZE_8;
	else
		size = effective_operand_size();

	fetch_mod_rm(&counter, (enum operand_t *) &operation, &operand_b, &addr, &segment);

	if (operation != 0x2 && operation != 0x3)
		operand_b = OPERAND_IMMEDIATE;

	switch (operation & 7) {
		case 0x0: test(counter, operand_a, size, operand_b, size, addr, segment); break;
		case 0x1: ERROR(); break;
		case 0x2: not(counter, operand_a, size, addr, segment); break;
		case 0x3: neg(counter, operand_a, size, addr, segment); break;
		case 0x4: mul(counter, operand_a, size, operand_b, size, addr, segment); break;
		case 0x5: imul(counter, operand_a, size, operand_b, size, addr, segment); break;
		case 0x6: div(counter, operand_a, size, operand_b, size, addr, segment); break;
		case 0x7: idiv(counter, operand_a, size, operand_b, size, addr, segment); break;
		default: ERROR();
	}
}

/******************************************************************************
 *
 * Handler for Group 4 and Group 5
 *
 * Bits 5, 4 and 3 of ModR/M byte used as an opcode extension.
 *
 ******************************************************************************/
/*
static void handle_group_4_and_5(uint32_t counter, uint32_t opcode) {
	int operand_size, opcode_extension, mod, rm;

	if ((opcode & 1) == 0)
		operand_size = 1;
	else
		operand_size = prefix_operand_size_override ? 4 : 2;

	decode_mod_rm(GET(1), &mod, &opcode_extension, &rm);

	if (opcode_extension >= 2 && opcode == 0xfe) {
//		TODO
//		gen_exception(c, CPU_FAULT_UD, pc_start - s->cs_base);
//		return;
	}

	if (mod != 3) {
		// TODO load: gen_lea_modrm
		// set ADDR
	} else {
		gen_load_reg_t1(rm, op_size);
	}

	switch (opcode_extension) {
		case 0x0: // INC
			gen_inc((mod == 0x3) ? rm : OPERAND_ADDR, operand_size);
			break;
		case 0x1: // DEC
			gen_dec((mod == 0x3) ? rm : OPERAND_ADDR, operand_size);
			break;
		case 0x2: // CALL
			break;
	}
}
*/

/******************************************************************************
 *
 * Handler for INC general-purpose register
 *
 * Opcode: 0x40 ... 0x47
 *
 ******************************************************************************/

static void handle_inc_general_register(uint32_t counter, uint32_t opcode) {
	enum operand_t operand;
	enum size_t size;

	operand = opcode & 7;

	size = effective_operand_size();

	inc(counter, operand, size, 0, SEGMENT_NONE);
}

/******************************************************************************
 *
 * Handler for DEC general-purpose register
 *
 * Opcode: 0x48 ... 0x4f
 *
 ******************************************************************************/

static void handle_dec_general_register(uint32_t counter, uint32_t opcode) {
	enum operand_t operand;
	enum size_t size;

	operand = opcode & 7;

	size = effective_operand_size();

	dec(counter, operand, size, 0, SEGMENT_NONE);
}

/******************************************************************************
 *
 * Handler for PUSH general-purpose register
 *
 * Opcode: 0x50 ... 0x57
 *
 ******************************************************************************/

static void handle_push_general_register(uint32_t counter, uint32_t opcode) {
	enum size_t operand_size, stack_size;
	enum operand_t operand;

	operand = opcode & 7;

	operand_size = effective_operand_size();

	if (ss_size32)
		stack_size = SIZE_32;
	else
		stack_size = SIZE_16;

	push(counter, operand, operand_size, 0, SEGMENT_NONE, stack_size);
}

/******************************************************************************
 *
 * Handler for POP general-purpose register
 *
 * Opcode: 0x58 ... 0x5f
 *
 ******************************************************************************/

static void handle_pop_general_register(uint32_t counter, uint32_t opcode) {
	enum size_t operand_size, stack_size;
	enum operand_t operand;

	operand = opcode & 7;

	operand_size = effective_operand_size();

	if (ss_size32)
		stack_size = SIZE_32;
	else
		stack_size = SIZE_16;

	pop(counter, operand, operand_size, 0, SEGMENT_NONE, stack_size);
}

/******************************************************************************
 *
 * Handler for PUSH segment register
 *
 ******************************************************************************/

static void handle_push_segment_register(uint32_t counter, uint32_t opcode) {
	enum size_t operand_size, stack_size;
	enum operand_t operand;

	switch (opcode) {
		case 0x06: operand = OPERAND_ES; break;
		case 0x0e: operand = OPERAND_CS; break;
		case 0x16: operand = OPERAND_SS; break;
		case 0x1e: operand = OPERAND_DS; break;
		case 0x0fa0: operand = OPERAND_FS; break;
		case 0x0fa8: operand = OPERAND_GS; break;
		default: ERROR();
	}

	operand_size = effective_operand_size();

	if (ss_size32)
		stack_size = SIZE_32;
	else
		stack_size = SIZE_16;

	push(counter, operand, operand_size, 0, SEGMENT_NONE, stack_size);
}

/******************************************************************************
 *
 * Handler for POP segment register
 *
 ******************************************************************************/

static void handle_pop_segment_register(uint32_t counter, uint32_t opcode) {
	enum size_t operand_size, stack_size;
	enum operand_t operand;

	switch (opcode) {
		case 0x07: operand = OPERAND_ES; break;
		case 0x17: operand = OPERAND_SS; break;
		case 0x1f: operand = OPERAND_DS; break;
		case 0x0fa1: operand = OPERAND_FS; break;
		case 0x0fa8: operand = OPERAND_GS; break;
		default: ERROR();
	}

	operand_size = effective_operand_size();

	if (ss_size32)
		stack_size = SIZE_32;
	else
		stack_size = SIZE_16;

	pop(counter, operand, operand_size, 0, SEGMENT_NONE, stack_size);
}

/******************************************************************************
 *
 * Handler for MOV
 *
 ******************************************************************************/

static enum operand_t segment2operand(enum segment_t segment) {
	switch (segment) {
		case SEGMENT_NONE:
			ERROR();
		case SEGMENT_ES:
			return OPERAND_ES;
		case SEGMENT_CS:
			return OPERAND_CS;
		case SEGMENT_SS:
			return OPERAND_SS;
		case SEGMENT_DS:
			return OPERAND_DS;
		case SEGMENT_FS:
			return OPERAND_FS;
		case SEGMENT_GS:
			return OPERAND_GS;
		default:
			ERROR();
	}
}

static void handle_mov(uint32_t counter, uint32_t opcode) {
	enum operand_t operand_src, operand_dst;
	enum segment_t segment;
	enum segment_t sreg;
	enum size_t size;
	uint32_t addr;

	if (opcode == 0x8c || opcode == 0x8e)
		size = SIZE_16;
	else if (opcode == 0xb8 || (opcode & 1) == 1)
		size = effective_operand_size();
	else
		size = SIZE_8;

	switch (opcode) {
		case 0x88:
		case 0x89:
			fetch_mod_rm(&counter, &operand_src, &operand_dst, &addr, &segment);
			break;
		case 0x8a:
		case 0x8b:
			fetch_mod_rm(&counter, &operand_dst, &operand_src, &addr, &segment);
			break;
		case 0x8c:
			fetch_mod_rm(&counter, (enum operand_t *) &sreg, &operand_dst, &addr, &segment);
			if (sreg >= SEGMENT_INVALID)
				exception(EXCEPTION_UD, 0);
			operand_src = segment2operand(sreg);
			break;
		case 0x8e:
			fetch_mod_rm(&counter, (enum operand_t *) &sreg, &operand_src, &addr, &segment);
			if (sreg >= SEGMENT_INVALID || sreg == SEGMENT_CS)
				exception(EXCEPTION_UD, 0);
			operand_dst = segment2operand(sreg);
			break;
		case 0xa0: case 0xa1: case 0xa2: case 0xa3:
			addr = fetch(&counter, effective_address_size());
			if ((opcode & 2) == 0) {
				operand_src = OPERAND_ADDRESS;
				operand_dst = OPERAND_EAX;
			} else {
				operand_src = OPERAND_EAX;
				operand_dst = OPERAND_ADDRESS;
			}
			segment = SEGMENT_DS;
			break;
		case 0xb0: case 0xb1: case 0xb2: case 0xb3:
		case 0xb4: case 0xb5: case 0xb6: case 0xb7:
		case 0xb8: case 0xb9: case 0xba: case 0xbb:
		case 0xbc: case 0xbd: case 0xbe: case 0xbf:
			operand_dst = opcode & 7;
			operand_src = OPERAND_IMMEDIATE;
			break;
		case 0xc6:
		case 0xc7:
			fetch_mod_rm(&counter, &operand_dst, &operand_src, &addr, &segment);
			if (operand_src != 0)
				exception(EXCEPTION_UD, 0);
			operand_src = OPERAND_IMMEDIATE;
			break;
		default:
			ERROR();
	}

	mov(counter, operand_dst, size, operand_src, size, addr, segment);
}

/******************************************************************************
 *
 * Handler for Jcc - Jump if Condition Is Met
 *
 ******************************************************************************/

static void handle_jecxz(uint32_t counter, uint32_t opcode) {
	enum size_t size;

	size = effective_operand_size();

	jecxz(counter, size);
}

static void handle_jcc(uint32_t counter, uint32_t opcode) {
	enum size_t size;
	bool negate;
	enum jcc_t cc;

	negate = opcode & 1;

	cc = (enum jcc_t) ((opcode >> 1) & 7);

	switch (opcode >> 4) {
		case 0x7:
			size = SIZE_8;
			break;
		case 0x0f8:
			size = effective_operand_size();
			break;
		default:
			ERROR();
	}

	jcc(counter, cc, negate, size);
}

/******************************************************************************
 *
 * Exception handler
 *
 ******************************************************************************/

static void handle_exception(void) {
	// TODO
	exception_vector = EXCEPTION_NONE;
	ERROR();
}

/******************************************************************************
 *
 * Prefix Handler
 *
 ******************************************************************************/

static void handle_prefix(uint32_t counter, uint32_t opcode) {
	switch (opcode) {
		case 0x26: // ES segment override prefix
			prefix_segment_override = SEGMENT_ES;
			eip += counter;
			break;
		case 0x2e: // CS segment override prefix
			prefix_segment_override = SEGMENT_CS;
			eip += counter;
			break;
		case 0x36: // SS segment override prefix
			prefix_segment_override = SEGMENT_SS;
			eip += counter;
			break;
		case 0x3e: // DS segment override prefix
			prefix_segment_override = SEGMENT_DS;
			eip += counter;
			break;
		case 0x64: // FS segment override prefix
			prefix_segment_override = SEGMENT_FS;
			eip += counter;
			break;
		case 0x65: // GS segment override prefix
			prefix_segment_override = SEGMENT_GS;
			eip += counter;
			break;
		case 0x66: // Operand-size override prefix
			prefix_operand_size_override = true;
			eip += counter;
			break;
		case 0x67: // Address-size override prefix
			prefix_address_size_override = true;
			eip += counter;
			break;
		case 0xf0: // LOCK prefix
			lock();
			prefix_lock_repeat = LR_LOCK;
			eip += counter;
			break;
		case 0xf2: // REPNE/REPNZ prefix (used only with string instructions)
			if (prefix_lock_repeat == LR_LOCK)
				unlock();
			prefix_lock_repeat = LR_REPNZ;
			eip += counter;
			break;
		case 0xf3: // REP/REPE/REPZ prefix (used only with string instructions)
			if (prefix_lock_repeat == LR_LOCK)
				unlock();
			prefix_lock_repeat = LR_REPZ;
			eip += counter;
			break;
		default:
			ERROR();
	}
}

/******************************************************************************
 *
 * Handler for one instruction including prefixes
 *
 ******************************************************************************/

void handle_instruction(void) {
	uint32_t counter;
	uint32_t opcode;

	if (exception_vector != EXCEPTION_NONE) {
		handle_exception();
		return;
	}

	counter = 0;

	opcode = fetch(&counter, SIZE_8);

	switch (opcode) {
	case 0x00: handle_arithmetic_and_logic(counter, opcode); break; // ADD
	case 0x01: handle_arithmetic_and_logic(counter, opcode); break; // ADD
	case 0x02: handle_arithmetic_and_logic(counter, opcode); break; // ADD
	case 0x03: handle_arithmetic_and_logic(counter, opcode); break; // ADD
	case 0x04: handle_arithmetic_and_logic(counter, opcode); break; // ADD
	case 0x05: handle_arithmetic_and_logic(counter, opcode); break; // ADD
	case 0x06: handle_push_segment_register(counter, opcode); break; // PUSH ES
	case 0x07: handle_pop_segment_register(counter, opcode); break; // POP ES
	case 0x08: handle_arithmetic_and_logic(counter, opcode); break; // OR
	case 0x09: handle_arithmetic_and_logic(counter, opcode); break; // OR
	case 0x0a: handle_arithmetic_and_logic(counter, opcode); break; // OR
	case 0x0b: handle_arithmetic_and_logic(counter, opcode); break; // OR
	case 0x0c: handle_arithmetic_and_logic(counter, opcode); break; // OR
	case 0x0d: handle_arithmetic_and_logic(counter, opcode); break; // OR
	case 0x0e: handle_push_segment_register(counter, opcode); break; // PUSH CS
	case 0x0f: // two-byte opcode
		opcode = 0xf00 | fetch(&counter, SIZE_8);
		switch (opcode) {
		/* TODO */
		case 0x0f80: handle_jcc(counter, opcode); break; // JO
		case 0x0f81: handle_jcc(counter, opcode); break; // JNO
		case 0x0f82: handle_jcc(counter, opcode); break; // JB
		case 0x0f83: handle_jcc(counter, opcode); break; // JNB
		case 0x0f84: handle_jcc(counter, opcode); break; // JZ
		case 0x0f85: handle_jcc(counter, opcode); break; // JNZ
		case 0x0f86: handle_jcc(counter, opcode); break; // JBE
		case 0x0f87: handle_jcc(counter, opcode); break; // JNBE
		case 0x0f88: handle_jcc(counter, opcode); break; // JS
		case 0x0f89: handle_jcc(counter, opcode); break; // JNS
		case 0x0f8a: handle_jcc(counter, opcode); break; // JP
		case 0x0f8b: handle_jcc(counter, opcode); break; // JNP
		case 0x0f8c: handle_jcc(counter, opcode); break; // JL
		case 0x0f8d: handle_jcc(counter, opcode); break; // JNL
		case 0x0f8e: handle_jcc(counter, opcode); break; // JLE
		case 0x0f8f: handle_jcc(counter, opcode); break; // JNLE
		/* TODO */
		case 0x0fa0: handle_push_segment_register(counter, opcode); break; // PUSH FS
		case 0x0fa1: handle_pop_segment_register(counter, opcode); break; // POP FS
		/* TODO */
		case 0x0fa8: handle_push_segment_register(counter, opcode); break; // PUSH GS
		case 0x0fa9: handle_pop_segment_register(counter, opcode); break; // POP GS
		/* TODO */
		default: ERROR();
		}
		break;
	case 0x10: handle_arithmetic_and_logic(counter, opcode); break; // ADC
	case 0x11: handle_arithmetic_and_logic(counter, opcode); break; // ADC
	case 0x12: handle_arithmetic_and_logic(counter, opcode); break; // ADC
	case 0x13: handle_arithmetic_and_logic(counter, opcode); break; // ADC
	case 0x14: handle_arithmetic_and_logic(counter, opcode); break; // ADC
	case 0x15: handle_arithmetic_and_logic(counter, opcode); break; // ADC
	case 0x16: handle_push_segment_register(counter, opcode); break; // PUSH SS
	case 0x17: handle_pop_segment_register(counter, opcode); break; // POP SS
	case 0x18: handle_arithmetic_and_logic(counter, opcode); break; // SBB
	case 0x19: handle_arithmetic_and_logic(counter, opcode); break; // SBB
	case 0x1a: handle_arithmetic_and_logic(counter, opcode); break; // SBB
	case 0x1b: handle_arithmetic_and_logic(counter, opcode); break; // SBB
	case 0x1c: handle_arithmetic_and_logic(counter, opcode); break; // SBB
	case 0x1d: handle_arithmetic_and_logic(counter, opcode); break; // SBB
	case 0x1e: handle_push_segment_register(counter, opcode); break; // PUSH DS
	case 0x1f: handle_pop_segment_register(counter, opcode); break; // POP DS
	case 0x20: handle_arithmetic_and_logic(counter, opcode); break; // AND
	case 0x21: handle_arithmetic_and_logic(counter, opcode); break; // AND
	case 0x22: handle_arithmetic_and_logic(counter, opcode); break; // AND
	case 0x23: handle_arithmetic_and_logic(counter, opcode); break; // AND
	case 0x24: handle_arithmetic_and_logic(counter, opcode); break; // ADD
	case 0x25: handle_arithmetic_and_logic(counter, opcode); break; // AND
	case 0x26: handle_prefix(counter, opcode); return; // ES segment override prefix
	case 0x27: daa(counter); break; // DAA
	case 0x28: handle_arithmetic_and_logic(counter, opcode); break; // SUB
	case 0x29: handle_arithmetic_and_logic(counter, opcode); break; // SUB
	case 0x2a: handle_arithmetic_and_logic(counter, opcode); break; // SUB
	case 0x2b: handle_arithmetic_and_logic(counter, opcode); break; // SUB
	case 0x2c: handle_arithmetic_and_logic(counter, opcode); break; // SUB
	case 0x2d: handle_arithmetic_and_logic(counter, opcode); break; // SUB
	case 0x2e: handle_prefix(counter, opcode); return; // CS segment override prefix
	/* TODO: DAS  */
	case 0x30: handle_arithmetic_and_logic(counter, opcode); break; // XOR
	case 0x31: handle_arithmetic_and_logic(counter, opcode); break; // XOR
	case 0x32: handle_arithmetic_and_logic(counter, opcode); break; // XOR
	case 0x33: handle_arithmetic_and_logic(counter, opcode); break; // XOR
	case 0x34: handle_arithmetic_and_logic(counter, opcode); break; // XOR
	case 0x35: handle_arithmetic_and_logic(counter, opcode); break; // XOR
	case 0x36: handle_prefix(counter, opcode); return; // SS segment override prefix
	/* TODO: AAA */
	case 0x38: handle_arithmetic_and_logic(counter, opcode); break; // CMP
	case 0x39: handle_arithmetic_and_logic(counter, opcode); break; // CMP
	case 0x3a: handle_arithmetic_and_logic(counter, opcode); break; // CMP
	case 0x3b: handle_arithmetic_and_logic(counter, opcode); break; // CMP
	case 0x3c: handle_arithmetic_and_logic(counter, opcode); break; // CMP
	case 0x3d: handle_arithmetic_and_logic(counter, opcode); break; // CMP
	case 0x3e: handle_prefix(counter, opcode); return; // DS segment override prefix
	/* TODO: AAS */
	case 0x40: handle_inc_general_register(counter, opcode); break; // INC
	case 0x41: handle_inc_general_register(counter, opcode); break; // INC
	case 0x42: handle_inc_general_register(counter, opcode); break; // INC
	case 0x43: handle_inc_general_register(counter, opcode); break; // INC
	case 0x44: handle_inc_general_register(counter, opcode); break; // INC
	case 0x45: handle_inc_general_register(counter, opcode); break; // INC
	case 0x46: handle_inc_general_register(counter, opcode); break; // INC
	case 0x47: handle_inc_general_register(counter, opcode); break; // INC
	case 0x48: handle_dec_general_register(counter, opcode); break; // DEC
	case 0x49: handle_dec_general_register(counter, opcode); break; // DEC
	case 0x4a: handle_dec_general_register(counter, opcode); break; // DEC
	case 0x4b: handle_dec_general_register(counter, opcode); break; // DEC
	case 0x4c: handle_dec_general_register(counter, opcode); break; // DEC
	case 0x4d: handle_dec_general_register(counter, opcode); break; // DEC
	case 0x4e: handle_dec_general_register(counter, opcode); break; // DEC
	case 0x4f: handle_dec_general_register(counter, opcode); break; // DEC
	case 0x50: handle_push_general_register(counter, opcode); break; // PUSH
	case 0x51: handle_push_general_register(counter, opcode); break; // PUSH
	case 0x52: handle_push_general_register(counter, opcode); break; // PUSH
	case 0x53: handle_push_general_register(counter, opcode); break; // PUSH
	case 0x54: handle_push_general_register(counter, opcode); break; // PUSH
	case 0x55: handle_push_general_register(counter, opcode); break; // PUSH
	case 0x56: handle_push_general_register(counter, opcode); break; // PUSH
	case 0x57: handle_push_general_register(counter, opcode); break; // PUSH
	case 0x58: handle_pop_general_register(counter, opcode); break; // POP
	case 0x59: handle_pop_general_register(counter, opcode); break; // POP
	case 0x5a: handle_pop_general_register(counter, opcode); break; // POP
	case 0x5b: handle_pop_general_register(counter, opcode); break; // POP
	case 0x5c: handle_pop_general_register(counter, opcode); break; // POP
	case 0x5d: handle_pop_general_register(counter, opcode); break; // POP
	case 0x5e: handle_pop_general_register(counter, opcode); break;
	case 0x5f: handle_pop_general_register(counter, opcode); break; // POP
	/* TODO */
	case 0x64: handle_prefix(counter, opcode); return; // FS segment override prefix
	case 0x65: handle_prefix(counter, opcode); return; // GS segment override prefix
	case 0x66: handle_prefix(counter, opcode); return; // Operand-size override prefix
	case 0x67: handle_prefix(counter, opcode); return; // Address-size override prefix
	/* TODO */
	case 0x70: handle_jcc(counter, opcode); break; // JO
	case 0x71: handle_jcc(counter, opcode); break; // JNO
	case 0x72: handle_jcc(counter, opcode); break; // JB
	case 0x73: handle_jcc(counter, opcode); break; // JNB
	case 0x74: handle_jcc(counter, opcode); break; // JZ
	case 0x75: handle_jcc(counter, opcode); break; // JNZ
	case 0x76: handle_jcc(counter, opcode); break; // JBE
	case 0x77: handle_jcc(counter, opcode); break; // JNBE
	case 0x78: handle_jcc(counter, opcode); break; // JS
	case 0x79: handle_jcc(counter, opcode); break; // JNS
	case 0x7a: handle_jcc(counter, opcode); break; // JP
	case 0x7b: handle_jcc(counter, opcode); break; // JNP
	case 0x7c: handle_jcc(counter, opcode); break; // JL
	case 0x7d: handle_jcc(counter, opcode); break; // JNL
	case 0x7e: handle_jcc(counter, opcode); break; // JLE
	case 0x7f: handle_jcc(counter, opcode); break; // JNLE
	case 0x80: handle_group_1(counter, opcode); break; // ADD,OR,ADC,SBB,AND,SUB,XOR,CMP
	case 0x81: handle_group_1(counter, opcode); break; // ADD,OR,ADC,SBB,AND,SUB,XOR,CMP
	case 0x82: handle_group_1(counter, opcode); break; // ADD,OR,ADC,SBB,AND,SUB,XOR,CMP
	case 0x83: handle_group_1(counter, opcode); break; // ADD,OR,ADC,SBB,AND,SUB,XOR,CMP
	/* TODO */
	case 0x88: handle_mov(counter, opcode); break; // MOV
	case 0x89: handle_mov(counter, opcode); break; // MOV
	case 0x8a: handle_mov(counter, opcode); break; // MOV
	case 0x8b: handle_mov(counter, opcode); break; // MOV
	case 0x8c: handle_mov(counter, opcode); break; // MOV
	case 0x8e: handle_mov(counter, opcode); break; // MOV
	/* TODO */
	case 0x9f: lahf(counter); break; // LAHF
	/* TODO */
	case 0xa0: handle_mov(counter, opcode); break; // MOV
	case 0xa1: handle_mov(counter, opcode); break; // MOV
	case 0xa2: handle_mov(counter, opcode); break; // MOV
	case 0xa3: handle_mov(counter, opcode); break; // MOV
	/* TODO */
	case 0xb0: handle_mov(counter, opcode); break; // MOV
	case 0xb1: handle_mov(counter, opcode); break; // MOV
	case 0xb2: handle_mov(counter, opcode); break; // MOV
	case 0xb3: handle_mov(counter, opcode); break; // MOV
	case 0xb4: handle_mov(counter, opcode); break; // MOV
	case 0xb5: handle_mov(counter, opcode); break; // MOV
	case 0xb6: handle_mov(counter, opcode); break; // MOV
	case 0xb7: handle_mov(counter, opcode); break; // MOV
	case 0xb8: handle_mov(counter, opcode); break; // MOV
	case 0xb9: handle_mov(counter, opcode); break; // MOV
	case 0xba: handle_mov(counter, opcode); break; // MOV
	case 0xbb: handle_mov(counter, opcode); break; // MOV
	case 0xbc: handle_mov(counter, opcode); break; // MOV
	case 0xbd: handle_mov(counter, opcode); break; // MOV
	case 0xbe: handle_mov(counter, opcode); break; // MOV
	case 0xbf: handle_mov(counter, opcode); break; // MOV
	/* TODO */
	case 0xc6: handle_mov(counter, opcode); break; // MOV
	case 0xc7: handle_mov(counter, opcode); break; // MOV
	/* TODO */
	case 0xe3: handle_jecxz(counter, opcode); break; // JECXZ JCXZ
	/* TODO */
	case 0xf0: handle_prefix(counter, opcode); return; // LOCK prefix
	case 0xf1: exception(EXCEPTION_UD, 0); break; // Reserved and should not be used
	case 0xf2: handle_prefix(counter, opcode); return; // REPNE/REPNZ prefix (used only with string instructions)
	case 0xf3: handle_prefix(counter, opcode); return; // REP/REPE/REPZ prefix (used only with string instructions)
	/* TODO */
	case 0xf6: handle_group_3(counter, opcode); break; // TEST,NOT,NEG,MUL,IMUL,DIV,IDIV
	case 0xf7: handle_group_3(counter, opcode); break; // TEST,NOT,NEG,MUL,IMUL,DIV,IDIV
	/* TODO */
	default: ERROR();
	}

	if (prefix_lock_repeat == LR_LOCK)
		unlock();

	prefix_lock_repeat = LR_NONE;
	prefix_segment_override = SEGMENT_NONE;
	prefix_operand_size_override = false;
	prefix_address_size_override = false;
}

