view m68k_core_x86.c @ 581:9f40aa5243c2

Combine implementations of lea and pea. Update bit instructions to use the op_ family of functions to simplify their implementation a bit.
author Michael Pavone <pavone@retrodev.com>
date Wed, 05 Mar 2014 19:26:53 -0800
parents 5157bc966c1a
children c05fcbfe1b1a
line wrap: on
line source

/*
 Copyright 2013 Michael Pavone
 This file is part of BlastEm.
 BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text.
*/
#include "gen_x86.h"
#include "m68k_core.h"
#include "m68k_internal.h"
#include "68kinst.h"
#include "mem.h"
#include "backend.h"
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

#define CYCLES RAX
#define LIMIT RBP
#define CONTEXT RSI
#define SCRATCH1 RCX

#ifdef X86_64
#define SCRATCH2 RDI
#else
#define SCRATCH2 RBX
#endif

enum {
	FLAG_X,
	FLAG_N,
	FLAG_Z,
	FLAG_V,
	FLAG_C
};

void set_flag(m68k_options * opts, uint8_t val, uint8_t flag)
{
	if (opts->flag_regs[flag] >= 0) {
		mov_ir(&opts->gen.code, val, opts->flag_regs[flag], SZ_B);
	} else {
		int8_t offset = offsetof(m68k_context, flags) + flag;
		if (offset) {
			mov_irdisp(&opts->gen.code, val, opts->gen.context_reg, offset, SZ_B);
		} else {
			mov_irind(&opts->gen.code, val, opts->gen.context_reg, SZ_B);
		}
	}
}

void set_flag_cond(m68k_options *opts, uint8_t cond, uint8_t flag)
{
	if (opts->flag_regs[flag] >= 0) {
		setcc_r(&opts->gen.code, cond, opts->flag_regs[flag]);
	} else {
		int8_t offset = offsetof(m68k_context, flags) + flag;
		if (offset) {
			setcc_rdisp(&opts->gen.code, cond, opts->gen.context_reg, offset);
		} else {
			setcc_rind(&opts->gen.code, cond, opts->gen.context_reg);
		}
	}
}

void check_flag(m68k_options *opts, uint8_t flag)
{
	if (opts->flag_regs[flag] >= 0) {
		cmp_ir(&opts->gen.code, 0, opts->flag_regs[flag], SZ_B);
	} else {
		cmp_irdisp(&opts->gen.code, 0, opts->gen.context_reg, offsetof(m68k_context, flags) + flag, SZ_B);
	}
}

void flag_to_reg(m68k_options *opts, uint8_t flag, uint8_t reg)
{
	if (opts->flag_regs[flag] >= 0) {
		mov_rr(&opts->gen.code, opts->flag_regs[flag], reg, SZ_B);
	} else {
		int8_t offset = offsetof(m68k_context, flags) + flag;
		if (offset) {
			mov_rdispr(&opts->gen.code, opts->gen.context_reg, offset, reg, SZ_B);
		} else {
			mov_rindr(&opts->gen.code, opts->gen.context_reg, reg, SZ_B);
		}
	}
}

void reg_to_flag(m68k_options *opts, uint8_t reg, uint8_t flag)
{
	if (opts->flag_regs[flag] >= 0) {
		mov_rr(&opts->gen.code, reg, opts->flag_regs[flag], SZ_B);
	} else {
		int8_t offset = offsetof(m68k_context, flags) + flag;
		if (offset) {
			mov_rrdisp(&opts->gen.code, reg, opts->gen.context_reg, offset, SZ_B);
		} else {
			mov_rrind(&opts->gen.code, reg, opts->gen.context_reg, SZ_B);
		}
	}
}

void flag_to_flag(m68k_options *opts, uint8_t flag1, uint8_t flag2)
{
	code_info *code = &opts->gen.code;
	if (opts->flag_regs[flag1] >= 0 && opts->flag_regs[flag2] >= 0) {
		mov_rr(code, opts->flag_regs[flag1], opts->flag_regs[flag2], SZ_B);
	} else if(opts->flag_regs[flag1] >= 0) {
		mov_rrdisp(code, opts->flag_regs[flag1], opts->gen.context_reg, offsetof(m68k_context, flags) + flag2, SZ_B);
	} else if (opts->flag_regs[flag2] >= 0) {
		mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, flags) + flag1, opts->flag_regs[flag2], SZ_B);
	} else {
		push_r(code, opts->gen.scratch1);
		mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, flags) + flag1, opts->gen.scratch1, SZ_B);
		mov_rrdisp(code, opts->gen.scratch1, opts->gen.context_reg, offsetof(m68k_context, flags) + flag2, SZ_B);
		pop_r(code, opts->gen.scratch1);
	}
}

void update_flags(m68k_options *opts, uint32_t update_mask)
{
	uint8_t native_flags[] = {0, CC_S, CC_Z, CC_O, CC_C};
	for (int8_t flag = FLAG_C; flag >= FLAG_X; --flag)
	{
		if (update_mask & X0 << (flag*3)) {
			set_flag(opts, 0, flag);
		} else if(update_mask & X1 << (flag*3)) {
			set_flag(opts, 1, flag);
		} else if(update_mask & X << (flag*3)) {
			if (flag == FLAG_X) {
				if (opts->flag_regs[FLAG_C] >= 0 || !(update_mask & (C0|C1|C))) {
					flag_to_flag(opts, FLAG_C, FLAG_X);
				} else if(update_mask & C0) {
					set_flag(opts, 0, flag);
				} else if(update_mask & C1) {
					set_flag(opts, 1, flag);
				} else {
					set_flag_cond(opts, CC_C, flag);
				}
			} else {
				set_flag_cond(opts, native_flags[flag], flag);
			}
		}
	}
}

void flag_to_carry(m68k_options * opts, uint8_t flag)
{
	if (opts->flag_regs[flag] >= 0) {
		bt_ir(&opts->gen.code, 0, opts->flag_regs[flag], SZ_B);
	} else {
		bt_irdisp(&opts->gen.code, 0, opts->gen.context_reg, offsetof(m68k_context, flags) + flag, SZ_B);
	}
}

void or_flag_to_reg(m68k_options *opts, uint8_t flag, uint8_t reg)
{
	if (opts->flag_regs[flag] >= 0) {
		or_rr(&opts->gen.code, opts->flag_regs[flag], reg, SZ_B);
	} else {
		or_rdispr(&opts->gen.code, opts->gen.context_reg, offsetof(m68k_context, flags) + flag, reg, SZ_B);
	}
}

void xor_flag_to_reg(m68k_options *opts, uint8_t flag, uint8_t reg)
{
	if (opts->flag_regs[flag] >= 0) {
		xor_rr(&opts->gen.code, opts->flag_regs[flag], reg, SZ_B);
	} else {
		xor_rdispr(&opts->gen.code, opts->gen.context_reg, offsetof(m68k_context, flags) + flag, reg, SZ_B);
	}
}

void xor_flag(m68k_options *opts, uint8_t val, uint8_t flag)
{
	if (opts->flag_regs[flag] >= 0) {
		xor_ir(&opts->gen.code, val, opts->flag_regs[flag], SZ_B);
	} else {
		xor_irdisp(&opts->gen.code, val, opts->gen.context_reg, offsetof(m68k_context, flags) + flag, SZ_B);
	}
}

void cmp_flags(m68k_options *opts, uint8_t flag1, uint8_t flag2)
{
	code_info *code = &opts->gen.code;
	if (opts->flag_regs[flag1] >= 0 && opts->flag_regs[flag2] >= 0) {
		cmp_rr(code, opts->flag_regs[flag1], opts->flag_regs[flag2], SZ_B);
	} else if(opts->flag_regs[flag1] >= 0 || opts->flag_regs[flag2] >= 0) {
		if (opts->flag_regs[flag2] >= 0) {
			uint8_t tmp = flag1;
			flag1 = flag2;
			flag2 = tmp;
		}
		cmp_rrdisp(code, opts->flag_regs[flag1], opts->gen.context_reg, offsetof(m68k_context, flags) + flag2, SZ_B);
	} else {
		mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, flags) + flag1, opts->gen.scratch1, SZ_B);
		cmp_rrdisp(code, opts->gen.scratch1, opts->gen.context_reg, offsetof(m68k_context, flags) + flag2, SZ_B);
	}
}

void areg_to_native(m68k_options *opts, uint8_t reg, uint8_t native_reg)
{
	if (opts->aregs[reg] >= 0) {
		mov_rr(&opts->gen.code, opts->aregs[reg], native_reg, SZ_D);
	} else {
		mov_rdispr(&opts->gen.code, opts->gen.context_reg,  areg_offset(reg), native_reg, SZ_D);
	}
}

void dreg_to_native(m68k_options *opts, uint8_t reg, uint8_t native_reg)
{
	if (opts->dregs[reg] >= 0) {
		mov_rr(&opts->gen.code, opts->dregs[reg], native_reg, SZ_D);
	} else {
		mov_rdispr(&opts->gen.code, opts->gen.context_reg,  dreg_offset(reg), native_reg, SZ_D);
	}
}

void areg_to_native_sx(m68k_options *opts, uint8_t reg, uint8_t native_reg)
{
	if (opts->aregs[reg] >= 0) {
		movsx_rr(&opts->gen.code, opts->aregs[reg], native_reg, SZ_W, SZ_D);
	} else {
		movsx_rdispr(&opts->gen.code, opts->gen.context_reg,  areg_offset(reg), native_reg, SZ_W, SZ_D);
	}
}

void dreg_to_native_sx(m68k_options *opts, uint8_t reg, uint8_t native_reg)
{
	if (opts->dregs[reg] >= 0) {
		movsx_rr(&opts->gen.code, opts->dregs[reg], native_reg, SZ_W, SZ_D);
	} else {
		movsx_rdispr(&opts->gen.code, opts->gen.context_reg,  dreg_offset(reg), native_reg, SZ_W, SZ_D);
	}
}

void native_to_areg(m68k_options *opts, uint8_t native_reg, uint8_t reg)
{
	if (opts->aregs[reg] >= 0) {
		mov_rr(&opts->gen.code, native_reg, opts->aregs[reg], SZ_D);
	} else {
		mov_rrdisp(&opts->gen.code, native_reg, opts->gen.context_reg, areg_offset(reg), SZ_D);
	}
}

void native_to_dreg(m68k_options *opts, uint8_t native_reg, uint8_t reg)
{
	if (opts->dregs[reg] >= 0) {
		mov_rr(&opts->gen.code, native_reg, opts->dregs[reg], SZ_D);
	} else {
		mov_rrdisp(&opts->gen.code, native_reg, opts->gen.context_reg, dreg_offset(reg), SZ_D);
	}
}

void ldi_areg(m68k_options *opts, int32_t value, uint8_t reg)
{
	if (opts->aregs[reg] >= 0) {
		mov_ir(&opts->gen.code, value, opts->aregs[reg], SZ_D);
	} else {
		mov_irdisp(&opts->gen.code, value, opts->gen.context_reg, areg_offset(reg), SZ_D);
	}
}

void ldi_native(m68k_options *opts, int32_t value, uint8_t reg)
{
	mov_ir(&opts->gen.code, value, reg, SZ_D);
}

void addi_areg(m68k_options *opts, int32_t val, uint8_t reg)
{
	if (opts->aregs[reg] >= 0) {
		add_ir(&opts->gen.code, val, opts->aregs[reg], SZ_D);
	} else {
		add_irdisp(&opts->gen.code, val, opts->gen.context_reg, areg_offset(reg), SZ_D);
	}
}

void subi_areg(m68k_options *opts, int32_t val, uint8_t reg)
{
	if (opts->aregs[reg] >= 0) {
		sub_ir(&opts->gen.code, val, opts->aregs[reg], SZ_D);
	} else {
		sub_irdisp(&opts->gen.code, val, opts->gen.context_reg, areg_offset(reg), SZ_D);
	}
}

void add_areg_native(m68k_options *opts, uint8_t reg, uint8_t native_reg)
{
	if (opts->aregs[reg] >= 0) {
		add_rr(&opts->gen.code, opts->aregs[reg], native_reg, SZ_D);
	} else {
		add_rdispr(&opts->gen.code, opts->gen.context_reg, areg_offset(reg), native_reg, SZ_D);
	}
}

void add_dreg_native(m68k_options *opts, uint8_t reg, uint8_t native_reg)
{
	if (opts->dregs[reg] >= 0) {
		add_rr(&opts->gen.code, opts->dregs[reg], native_reg, SZ_D);
	} else {
		add_rdispr(&opts->gen.code, opts->gen.context_reg, dreg_offset(reg), native_reg, SZ_D);
	}
}

void calc_areg_displace(m68k_options *opts, m68k_op_info *op, uint8_t native_reg)
{
	areg_to_native(opts, op->params.regs.pri, native_reg);
	add_ir(&opts->gen.code, op->params.regs.displacement, native_reg, SZ_D);
}

void calc_index_disp8(m68k_options *opts, m68k_op_info *op, uint8_t native_reg)
{
	uint8_t sec_reg = (op->params.regs.sec >> 1) & 0x7;
	if (op->params.regs.sec & 1) {
		if (op->params.regs.sec & 0x10) {
			add_areg_native(opts, sec_reg, native_reg);
		} else {
			add_dreg_native(opts, sec_reg, native_reg);
		}
	} else {
		uint8_t other_reg = native_reg == opts->gen.scratch1 ? opts->gen.scratch2 : opts->gen.scratch1;
		if (op->params.regs.sec & 0x10) {
			areg_to_native_sx(opts, sec_reg, other_reg);
		} else {
			dreg_to_native_sx(opts, sec_reg, other_reg);
		}
		add_rr(&opts->gen.code, other_reg, native_reg, SZ_D);
	}
	if (op->params.regs.displacement) {
		add_ir(&opts->gen.code, op->params.regs.displacement, native_reg, SZ_D);
	}
}

void calc_areg_index_disp8(m68k_options *opts, m68k_op_info *op, uint8_t native_reg)
{
	areg_to_native(opts, op->params.regs.pri, native_reg);
	calc_index_disp8(opts, op, native_reg);
}

void translate_m68k_op(m68kinst * inst, x86_ea * ea, m68k_options * opts, uint8_t dst)
{
	code_info *code = &opts->gen.code;
	m68k_op_info *op = dst ? &inst->dst : &inst->src;
	int8_t reg = native_reg(op, opts);
	uint8_t sec_reg;
	int32_t dec_amount,inc_amount;
	if (reg >= 0) {
		ea->mode = MODE_REG_DIRECT;
		if (!dst && inst->dst.addr_mode == MODE_AREG && inst->extra.size == OPSIZE_WORD) {
			movsx_rr(code, reg, opts->gen.scratch1, SZ_W, SZ_D);
			ea->base = opts->gen.scratch1;
		} else {
			ea->base = reg;
		}
		return;
	}
	switch (op->addr_mode)
	{
	case MODE_REG:
	case MODE_AREG:
		//We only get one memory parameter, so if the dst operand is a register in memory,
		//we need to copy this to a temp register first if we're translating the src operand
		if (dst || native_reg(&(inst->dst), opts) >= 0 || inst->dst.addr_mode == MODE_UNUSED || !(inst->dst.addr_mode == MODE_REG || inst->dst.addr_mode == MODE_AREG)
		    || inst->op == M68K_EXG) {

			ea->mode = MODE_REG_DISPLACE8;
			ea->base = opts->gen.context_reg;
			ea->disp = reg_offset(op);
		} else {
			if (inst->dst.addr_mode == MODE_AREG && inst->extra.size == OPSIZE_WORD) {
				movsx_rdispr(code, opts->gen.context_reg, reg_offset(op), opts->gen.scratch1, SZ_W, SZ_D);
			} else {
				mov_rdispr(code, opts->gen.context_reg, reg_offset(op), opts->gen.scratch1, inst->extra.size);
			}
			ea->mode = MODE_REG_DIRECT;
			ea->base = opts->gen.scratch1;
			//we're explicitly handling the areg dest here, so we exit immediately
			return;
		}
		break;
	case MODE_AREG_PREDEC:
		if (dst && inst->src.addr_mode == MODE_AREG_PREDEC) {
			push_r(code, opts->gen.scratch1);
		}
		dec_amount = inst->extra.size == OPSIZE_WORD ? 2 : (inst->extra.size == OPSIZE_LONG ? 4 : (op->params.regs.pri == 7 ? 2 :1));
		if (!dst) {
			cycles(&opts->gen, PREDEC_PENALTY);
		}
		subi_areg(opts, dec_amount, op->params.regs.pri);
	case MODE_AREG_INDIRECT:
	case MODE_AREG_POSTINC:
		areg_to_native(opts, op->params.regs.pri, opts->gen.scratch1);
		m68k_read_size(opts, inst->extra.size);

		if (dst) {
			if (inst->src.addr_mode == MODE_AREG_PREDEC) {
				//restore src operand to opts->gen.scratch2
				pop_r(code, opts->gen.scratch2);
			} else {
				//save reg value in opts->gen.scratch2 so we can use it to save the result in memory later
				areg_to_native(opts, op->params.regs.pri, opts->gen.scratch2);
			}
		}

		if (op->addr_mode == MODE_AREG_POSTINC) {
			inc_amount = inst->extra.size == OPSIZE_WORD ? 2 : (inst->extra.size == OPSIZE_LONG ? 4 : (op->params.regs.pri == 7 ? 2 : 1));
			addi_areg(opts, inc_amount, op->params.regs.pri);
		}
		ea->mode = MODE_REG_DIRECT;
		ea->base = (!dst && inst->dst.addr_mode == MODE_AREG_PREDEC && inst->op != M68K_MOVE) ? opts->gen.scratch2 : opts->gen.scratch1;
		break;
	case MODE_AREG_DISPLACE:
		cycles(&opts->gen, BUS);
		calc_areg_displace(opts, op, opts->gen.scratch1);
		if (dst) {
			push_r(code, opts->gen.scratch1);
		}
		m68k_read_size(opts, inst->extra.size);
		if (dst) {
			pop_r(code, opts->gen.scratch2);
		}

		ea->mode = MODE_REG_DIRECT;
		ea->base = opts->gen.scratch1;
		break;
	case MODE_AREG_INDEX_DISP8:
		cycles(&opts->gen, 6);
		calc_areg_index_disp8(opts, op, opts->gen.scratch1);
		if (dst) {
			push_r(code, opts->gen.scratch1);
		}
		m68k_read_size(opts, inst->extra.size);
		if (dst) {
			pop_r(code, opts->gen.scratch2);
		}

		ea->mode = MODE_REG_DIRECT;
		ea->base = opts->gen.scratch1;
		break;
	case MODE_PC_DISPLACE:
		cycles(&opts->gen, BUS);
		mov_ir(code, op->params.regs.displacement + inst->address+2, opts->gen.scratch1, SZ_D);
		if (dst) {
			push_r(code, opts->gen.scratch1);
		}
		m68k_read_size(opts, inst->extra.size);
		if (dst) {
			pop_r(code, opts->gen.scratch2);
		}

		ea->mode = MODE_REG_DIRECT;
		ea->base = opts->gen.scratch1;
		break;
	case MODE_PC_INDEX_DISP8:
		cycles(&opts->gen, 6);
		mov_ir(code, inst->address+2, opts->gen.scratch1, SZ_D);
		calc_index_disp8(opts, op, opts->gen.scratch1);
		if (dst) {
			push_r(code, opts->gen.scratch1);
		}
		m68k_read_size(opts, inst->extra.size);
		if (dst) {
			pop_r(code, opts->gen.scratch2);
		}

		ea->mode = MODE_REG_DIRECT;
		ea->base = opts->gen.scratch1;
		break;
	case MODE_ABSOLUTE:
	case MODE_ABSOLUTE_SHORT:
		cycles(&opts->gen, op->addr_mode == MODE_ABSOLUTE ? BUS*2 : BUS);
		mov_ir(code, op->params.immed, opts->gen.scratch1, SZ_D);
		if (dst) {
			push_r(code, opts->gen.scratch1);
		}
		m68k_read_size(opts, inst->extra.size);
		if (dst) {
			pop_r(code, opts->gen.scratch2);
		}

		ea->mode = MODE_REG_DIRECT;
		ea->base = opts->gen.scratch1;
		break;
	case MODE_IMMEDIATE:
	case MODE_IMMEDIATE_WORD:
		if (inst->variant != VAR_QUICK) {
			cycles(&opts->gen, (inst->extra.size == OPSIZE_LONG && op->addr_mode == MODE_IMMEDIATE) ? BUS*2 : BUS);
		}
		ea->mode = MODE_IMMED;
		ea->disp = op->params.immed;
		//sign extend value when the destination is an address register
		if (inst->dst.addr_mode == MODE_AREG && inst->extra.size == OPSIZE_WORD && ea->disp & 0x8000) {
			ea->disp |= 0xFFFF0000;
		}
		return;
	default:
		m68k_disasm(inst, disasm_buf);
		printf("%X: %s\naddress mode %d not implemented (%s)\n", inst->address, disasm_buf, op->addr_mode, dst ? "dst" : "src");
		exit(1);
	}
	if (!dst && inst->dst.addr_mode == MODE_AREG && inst->extra.size == OPSIZE_WORD) {
		if (ea->mode == MODE_REG_DIRECT) {
			movsx_rr(code, ea->base, opts->gen.scratch1, SZ_W, SZ_D);
		} else {
			movsx_rdispr(code, ea->base, ea->disp, opts->gen.scratch1, SZ_W, SZ_D);
			ea->mode = MODE_REG_DIRECT;
		}
		ea->base = opts->gen.scratch1;
	}
}

void m68k_save_result(m68kinst * inst, m68k_options * opts)
{
	code_info *code = &opts->gen.code;
	if (inst->dst.addr_mode != MODE_REG && inst->dst.addr_mode != MODE_AREG && inst->dst.addr_mode != MODE_UNUSED) {
		if (inst->dst.addr_mode == MODE_AREG_PREDEC && inst->src.addr_mode == MODE_AREG_PREDEC && inst->op != M68K_MOVE) {
			areg_to_native(opts, inst->dst.params.regs.pri, opts->gen.scratch2);
		}
		switch (inst->extra.size)
		{
		case OPSIZE_BYTE:
			call(code, opts->write_8);
			break;
		case OPSIZE_WORD:
			call(code, opts->write_16);
			break;
		case OPSIZE_LONG:
			call(code, opts->write_32_lowfirst);
			break;
		}
	}
}

void translate_m68k_move(m68k_options * opts, m68kinst * inst)
{
	code_info *code = &opts->gen.code;
	int8_t reg, flags_reg, sec_reg;
	uint8_t dir = 0;
	int32_t offset;
	int32_t inc_amount, dec_amount;
	x86_ea src;
	translate_m68k_op(inst, &src, opts, 0);
	reg = native_reg(&(inst->dst), opts);

	if (inst->dst.addr_mode != MODE_AREG) {
		if (src.mode == MODE_REG_DIRECT) {
			flags_reg = src.base;
		} else {
			if (reg >= 0) {
				flags_reg = reg;
			} else {
				if(src.mode == MODE_REG_DISPLACE8) {
					mov_rdispr(code, src.base, src.disp, opts->gen.scratch1, inst->extra.size);
				} else {
					mov_ir(code, src.disp, opts->gen.scratch1, inst->extra.size);
				}
				src.mode = MODE_REG_DIRECT;
				flags_reg = src.base = opts->gen.scratch1;
			}
		}
	}
	uint8_t size = inst->extra.size;
	switch(inst->dst.addr_mode)
	{
	case MODE_AREG:
		size = OPSIZE_LONG;
	case MODE_REG:
		if (reg >= 0) {
			if (src.mode == MODE_REG_DIRECT) {
				mov_rr(code, src.base, reg, size);
			} else if (src.mode == MODE_REG_DISPLACE8) {
				mov_rdispr(code, src.base, src.disp, reg, size);
			} else {
				mov_ir(code, src.disp, reg, size);
			}
		} else if(src.mode == MODE_REG_DIRECT) {
			mov_rrdisp(code, src.base, opts->gen.context_reg, reg_offset(&(inst->dst)), size);
		} else {
			mov_irdisp(code, src.disp, opts->gen.context_reg, reg_offset(&(inst->dst)), size);
		}
		break;
	case MODE_AREG_PREDEC:
		dec_amount = inst->extra.size == OPSIZE_WORD ? 2 : (inst->extra.size == OPSIZE_LONG ? 4 : (inst->dst.params.regs.pri == 7 ? 2 : 1));
		subi_areg(opts, dec_amount, inst->dst.params.regs.pri);
	case MODE_AREG_INDIRECT:
	case MODE_AREG_POSTINC:
		areg_to_native(opts, inst->dst.params.regs.pri, opts->gen.scratch2);
		if (src.mode == MODE_REG_DIRECT) {
			if (src.base != opts->gen.scratch1) {
				mov_rr(code, src.base, opts->gen.scratch1, inst->extra.size);
			}
		} else if (src.mode == MODE_REG_DISPLACE8) {
			mov_rdispr(code, src.base, src.disp, opts->gen.scratch1, inst->extra.size);
		} else {
			mov_ir(code, src.disp, opts->gen.scratch1, inst->extra.size);
		}
		break;
	case MODE_AREG_DISPLACE:
		cycles(&opts->gen, BUS);
		calc_areg_displace(opts, &inst->dst, opts->gen.scratch2);
		if (src.mode == MODE_REG_DIRECT) {
			if (src.base != opts->gen.scratch1) {
				mov_rr(code, src.base, opts->gen.scratch1, inst->extra.size);
			}
		} else if (src.mode == MODE_REG_DISPLACE8) {
			mov_rdispr(code, src.base, src.disp, opts->gen.scratch1, inst->extra.size);
		} else {
			mov_ir(code, src.disp, opts->gen.scratch1, inst->extra.size);
		}
		break;
	case MODE_AREG_INDEX_DISP8:
		cycles(&opts->gen, 6);//TODO: Check to make sure this is correct
		//calc_areg_index_disp8 will clober scratch1 when a 16-bit index is used
		if (src.base == opts->gen.scratch1 && !(inst->dst.params.regs.sec & 1)) {
			push_r(code, opts->gen.scratch1);
		}
		calc_areg_index_disp8(opts, &inst->dst, opts->gen.scratch2);
		if (src.base == opts->gen.scratch1 && !(inst->dst.params.regs.sec & 1)) {
			pop_r(code, opts->gen.scratch1);
		}
		if (src.mode == MODE_REG_DIRECT) {
			if (src.base != opts->gen.scratch1) {
				mov_rr(code, src.base, opts->gen.scratch1, inst->extra.size);
			}
		} else if (src.mode == MODE_REG_DISPLACE8) {
			mov_rdispr(code, src.base, src.disp, opts->gen.scratch1, inst->extra.size);
		} else {
			mov_ir(code, src.disp, opts->gen.scratch1, inst->extra.size);
		}
		break;
	case MODE_PC_DISPLACE:
		cycles(&opts->gen, BUS);
		mov_ir(code, inst->dst.params.regs.displacement + inst->address+2, opts->gen.scratch2, SZ_D);
		if (src.mode == MODE_REG_DIRECT) {
			if (src.base != opts->gen.scratch1) {
				mov_rr(code, src.base, opts->gen.scratch1, inst->extra.size);
			}
		} else if (src.mode == MODE_REG_DISPLACE8) {
			mov_rdispr(code, src.base, src.disp, opts->gen.scratch1, inst->extra.size);
		} else {
			mov_ir(code, src.disp, opts->gen.scratch1, inst->extra.size);
		}
		break;
	case MODE_PC_INDEX_DISP8:
		cycles(&opts->gen, 6);//TODO: Check to make sure this is correct
		mov_ir(code, inst->address, opts->gen.scratch2, SZ_D);
		if (src.base == opts->gen.scratch1 && !(inst->dst.params.regs.sec & 1)) {
			push_r(code, opts->gen.scratch1);
		}
		calc_index_disp8(opts, &inst->dst, opts->gen.scratch2);
		if (src.base == opts->gen.scratch1 && !(inst->dst.params.regs.sec & 1)) {
			pop_r(code, opts->gen.scratch1);
		}
		if (src.mode == MODE_REG_DIRECT) {
			if (src.base != opts->gen.scratch1) {
				mov_rr(code, src.base, opts->gen.scratch1, inst->extra.size);
			}
		} else if (src.mode == MODE_REG_DISPLACE8) {
			mov_rdispr(code, src.base, src.disp, opts->gen.scratch1, inst->extra.size);
		} else {
			mov_ir(code, src.disp, opts->gen.scratch1, inst->extra.size);
		}
		break;
	case MODE_ABSOLUTE:
	case MODE_ABSOLUTE_SHORT:
		if (src.mode == MODE_REG_DIRECT) {
			if (src.base != opts->gen.scratch1) {
				mov_rr(code, src.base, opts->gen.scratch1, inst->extra.size);
			}
		} else if (src.mode == MODE_REG_DISPLACE8) {
			mov_rdispr(code, src.base, src.disp, opts->gen.scratch1, inst->extra.size);
		} else {
			mov_ir(code, src.disp, opts->gen.scratch1, inst->extra.size);
		}
		if (inst->dst.addr_mode == MODE_ABSOLUTE) {
			cycles(&opts->gen, BUS*2);
		} else {
			cycles(&opts->gen, BUS);
		}
		mov_ir(code, inst->dst.params.immed, opts->gen.scratch2, SZ_D);
		break;
	default:
		m68k_disasm(inst, disasm_buf);
		printf("%X: %s\naddress mode %d not implemented (move dst)\n", inst->address, disasm_buf, inst->dst.addr_mode);
		exit(1);
	}

	if (inst->dst.addr_mode != MODE_AREG) {
		cmp_ir(code, 0, flags_reg, inst->extra.size);
		update_flags(opts, N|Z|V0|C0);
	}
	if (inst->dst.addr_mode != MODE_REG && inst->dst.addr_mode != MODE_AREG) {
		m68k_write_size(opts, inst->extra.size);
		if (inst->dst.addr_mode == MODE_AREG_POSTINC) {
			inc_amount = inst->extra.size == OPSIZE_WORD ? 2 : (inst->extra.size == OPSIZE_LONG ? 4 : (inst->dst.params.regs.pri == 7 ? 2 : 1));
			addi_areg(opts, inc_amount, inst->dst.params.regs.pri);
		}
	}

	//add cycles for prefetch
	cycles(&opts->gen, BUS);
}

void translate_m68k_movem(m68k_options * opts, m68kinst * inst)
{
	code_info *code = &opts->gen.code;
	int8_t bit,reg,sec_reg;
	uint8_t early_cycles;
	if(inst->src.addr_mode == MODE_REG) {
		//reg to mem
		early_cycles = 8;
		int8_t dir;
		switch (inst->dst.addr_mode)
		{
		case MODE_AREG_INDIRECT:
		case MODE_AREG_PREDEC:
			areg_to_native(opts, inst->dst.params.regs.pri, opts->gen.scratch2);
			break;
		case MODE_AREG_DISPLACE:
			early_cycles += BUS;
			calc_areg_displace(opts, &inst->dst, opts->gen.scratch2);
			break;
		case MODE_AREG_INDEX_DISP8:
			early_cycles += 6;
			calc_areg_index_disp8(opts, &inst->dst, opts->gen.scratch2);
			break;
		case MODE_PC_DISPLACE:
			early_cycles += BUS;
			mov_ir(code, inst->dst.params.regs.displacement + inst->address+2, opts->gen.scratch2, SZ_D);
			break;
		case MODE_PC_INDEX_DISP8:
			early_cycles += 6;
			mov_ir(code, inst->address+2, opts->gen.scratch2, SZ_D);
			calc_index_disp8(opts, &inst->dst, opts->gen.scratch2);
		case MODE_ABSOLUTE:
			early_cycles += 4;
		case MODE_ABSOLUTE_SHORT:
			early_cycles += 4;
			mov_ir(code, inst->dst.params.immed, opts->gen.scratch2, SZ_D);
			break;
		default:
			m68k_disasm(inst, disasm_buf);
			printf("%X: %s\naddress mode %d not implemented (movem dst)\n", inst->address, disasm_buf, inst->dst.addr_mode);
			exit(1);
		}
		if (inst->dst.addr_mode == MODE_AREG_PREDEC) {
			reg = 15;
			dir = -1;
		} else {
			reg = 0;
			dir = 1;
		}
		cycles(&opts->gen, early_cycles);
		for(bit=0; reg < 16 && reg >= 0; reg += dir, bit++) {
			if (inst->src.params.immed & (1 << bit)) {
				if (inst->dst.addr_mode == MODE_AREG_PREDEC) {
					sub_ir(code, (inst->extra.size == OPSIZE_LONG) ? 4 : 2, opts->gen.scratch2, SZ_D);
				}
				push_r(code, opts->gen.scratch2);
				if (reg > 7) {
					areg_to_native(opts, reg-8, opts->gen.scratch1);
				} else {
					dreg_to_native(opts, reg, opts->gen.scratch1);
				}
				if (inst->extra.size == OPSIZE_LONG) {
					call(code, opts->write_32_lowfirst);
				} else {
					call(code, opts->write_16);
				}
				pop_r(code, opts->gen.scratch2);
				if (inst->dst.addr_mode != MODE_AREG_PREDEC) {
					add_ir(code, (inst->extra.size == OPSIZE_LONG) ? 4 : 2, opts->gen.scratch2, SZ_D);
				}
			}
		}
		if (inst->dst.addr_mode == MODE_AREG_PREDEC) {
			native_to_areg(opts, opts->gen.scratch2, inst->dst.params.regs.pri);
		}
	} else {
		//mem to reg
		early_cycles = 4;
		switch (inst->src.addr_mode)
		{
		case MODE_AREG_INDIRECT:
		case MODE_AREG_POSTINC:
			areg_to_native(opts, inst->src.params.regs.pri, opts->gen.scratch1);
			break;
		case MODE_AREG_DISPLACE:
			early_cycles += BUS;
			reg = opts->gen.scratch2;
			calc_areg_displace(opts, &inst->src, opts->gen.scratch1);
			break;
		case MODE_AREG_INDEX_DISP8:
			early_cycles += 6;
			calc_areg_index_disp8(opts, &inst->src, opts->gen.scratch1);
			break;
		case MODE_PC_DISPLACE:
			early_cycles += BUS;
			mov_ir(code, inst->src.params.regs.displacement + inst->address+2, opts->gen.scratch1, SZ_D);
			break;
		case MODE_PC_INDEX_DISP8:
			early_cycles += 6;
			mov_ir(code, inst->address+2, opts->gen.scratch1, SZ_D);
			calc_index_disp8(opts, &inst->src, opts->gen.scratch1);
			break;
		case MODE_ABSOLUTE:
			early_cycles += 4;
		case MODE_ABSOLUTE_SHORT:
			early_cycles += 4;
			mov_ir(code, inst->src.params.immed, opts->gen.scratch1, SZ_D);
			break;
		default:
			m68k_disasm(inst, disasm_buf);
			printf("%X: %s\naddress mode %d not implemented (movem src)\n", inst->address, disasm_buf, inst->src.addr_mode);
			exit(1);
		}
		cycles(&opts->gen, early_cycles);
		for(reg = 0; reg < 16; reg ++) {
			if (inst->dst.params.immed & (1 << reg)) {
				push_r(code, opts->gen.scratch1);
				if (inst->extra.size == OPSIZE_LONG) {
					call(code, opts->read_32);
				} else {
					call(code, opts->read_16);
				}
				if (inst->extra.size == OPSIZE_WORD) {
					movsx_rr(code, opts->gen.scratch1, opts->gen.scratch1, SZ_W, SZ_D);
				}
				if (reg > 7) {
					native_to_areg(opts, opts->gen.scratch1, reg-8);
				} else {
					native_to_dreg(opts, opts->gen.scratch1, reg);
				}
				pop_r(code, opts->gen.scratch1);
				add_ir(code, (inst->extra.size == OPSIZE_LONG) ? 4 : 2, opts->gen.scratch1, SZ_D);
			}
		}
		if (inst->src.addr_mode == MODE_AREG_POSTINC) {
			native_to_areg(opts, opts->gen.scratch1, inst->src.params.regs.pri);
		}
	}
	//prefetch
	cycles(&opts->gen, 4);
}

void translate_m68k_clr(m68k_options * opts, m68kinst * inst)
{
	code_info *code = &opts->gen.code;
	update_flags(opts, N0|V0|C0|Z1);
	int8_t reg = native_reg(&(inst->dst), opts);
	if (reg >= 0) {
		cycles(&opts->gen, (inst->extra.size == OPSIZE_LONG ? 6 : 4));
		xor_rr(code, reg, reg, inst->extra.size);
		return;
	}
	x86_ea dst_op;
	//TODO: fix timing
	translate_m68k_op(inst, &dst_op, opts, 1);
	if (dst_op.mode == MODE_REG_DIRECT) {
		xor_rr(code, dst_op.base, dst_op.base, inst->extra.size);
	} else {
		mov_irdisp(code, 0, dst_op.base, dst_op.disp, inst->extra.size);
	}
	m68k_save_result(inst, opts);
}

void translate_m68k_ext(m68k_options * opts, m68kinst * inst)
{
	code_info *code = &opts->gen.code;
	x86_ea dst_op;
	uint8_t dst_size = inst->extra.size;
	inst->extra.size--;
	translate_m68k_op(inst, &dst_op, opts, 1);
	if (dst_op.mode == MODE_REG_DIRECT) {
		movsx_rr(code, dst_op.base, dst_op.base, inst->extra.size, dst_size);
		cmp_ir(code, 0, dst_op.base, dst_size);
	} else {
		movsx_rdispr(code, dst_op.base, dst_op.disp, opts->gen.scratch1, inst->extra.size, dst_size);
		cmp_ir(code, 0, opts->gen.scratch1, dst_size);
		mov_rrdisp(code, opts->gen.scratch1, dst_op.base, dst_op.disp, dst_size);
	}
	inst->extra.size = dst_size;
	update_flags(opts, N|V0|C0|Z);
	//M68K EXT only operates on registers so no need for a call to save result here
}

uint8_t m68k_eval_cond(m68k_options * opts, uint8_t cc)
{
	uint8_t cond = CC_NZ;
	switch (cc)
	{
	case COND_HIGH:
		cond = CC_Z;
	case COND_LOW_SAME:
		flag_to_reg(opts, FLAG_Z, opts->gen.scratch1);
		or_flag_to_reg(opts, FLAG_C, opts->gen.scratch1);
		break;
	case COND_CARRY_CLR:
		cond = CC_Z;
	case COND_CARRY_SET:
		check_flag(opts, FLAG_C);
		break;
	case COND_NOT_EQ:
		cond = CC_Z;
	case COND_EQ:
		check_flag(opts, FLAG_Z);
		break;
	case COND_OVERF_CLR:
		cond = CC_Z;
	case COND_OVERF_SET:
		check_flag(opts, FLAG_V);
		break;
	case COND_PLUS:
		cond = CC_Z;
	case COND_MINUS:
		check_flag(opts, FLAG_N);
		break;
	case COND_GREATER_EQ:
		cond = CC_Z;
	case COND_LESS:
		cmp_flags(opts, FLAG_N, FLAG_V);
		break;
	case COND_GREATER:
		cond = CC_Z;
	case COND_LESS_EQ:
		flag_to_reg(opts, FLAG_V, opts->gen.scratch1);
		xor_flag_to_reg(opts, FLAG_N, opts->gen.scratch1);
		or_flag_to_reg(opts, FLAG_Z, opts->gen.scratch1);
		break;
	}
	return cond;
}

void translate_m68k_bcc(m68k_options * opts, m68kinst * inst)
{
	code_info *code = &opts->gen.code;
	cycles(&opts->gen, 10);//TODO: Adjust this for branch not taken case
	int32_t disp = inst->src.params.immed;
	uint32_t after = inst->address + 2;
	if (inst->extra.cond == COND_TRUE) {
		jump_m68k_abs(opts, after + disp);
	} else {
		code_ptr dest_addr = get_native_address(opts->gen.native_code_map, after + disp);
		uint8_t cond = m68k_eval_cond(opts, inst->extra.cond);
		if (!dest_addr) {
			opts->gen.deferred = defer_address(opts->gen.deferred, after + disp, code->cur + 2);
			//dummy address to be replaced later, make sure it generates a 4-byte displacement
			dest_addr = code->cur + 256;
		}
		jcc(code, cond, dest_addr);
	}
}

void translate_m68k_scc(m68k_options * opts, m68kinst * inst)
{
	code_info *code = &opts->gen.code;
	uint8_t cond = inst->extra.cond;
	x86_ea dst_op;
	inst->extra.size = OPSIZE_BYTE;
	translate_m68k_op(inst, &dst_op, opts, 1);
	if (cond == COND_TRUE || cond == COND_FALSE) {
		if ((inst->dst.addr_mode == MODE_REG || inst->dst.addr_mode == MODE_AREG) && inst->extra.cond == COND_TRUE) {
			cycles(&opts->gen, 6);
		} else {
			cycles(&opts->gen, BUS);
		}
		if (dst_op.mode == MODE_REG_DIRECT) {
			mov_ir(code, cond == COND_TRUE ? 0xFF : 0, dst_op.base, SZ_B);
		} else {
			mov_irdisp(code, cond == COND_TRUE ? 0xFF : 0, dst_op.base, dst_op.disp, SZ_B);
		}
	} else {
		uint8_t cc = m68k_eval_cond(opts, cond);
		check_alloc_code(code, 6*MAX_INST_LEN);
		code_ptr true_off = code->cur + 1;
		jcc(code, cc, code->cur+2);
		cycles(&opts->gen, BUS);
		if (dst_op.mode == MODE_REG_DIRECT) {
			mov_ir(code, 0, dst_op.base, SZ_B);
		} else {
			mov_irdisp(code, 0, dst_op.base, dst_op.disp, SZ_B);
		}
		code_ptr end_off = code->cur+1;
		jmp(code, code->cur+2);
		*true_off = code->cur - (true_off+1);
		cycles(&opts->gen, 6);
		if (dst_op.mode == MODE_REG_DIRECT) {
			mov_ir(code, 0xFF, dst_op.base, SZ_B);
		} else {
			mov_irdisp(code, 0xFF, dst_op.base, dst_op.disp, SZ_B);
		}
		*end_off = code->cur - (end_off+1);
	}
	m68k_save_result(inst, opts);
}

void translate_m68k_dbcc(m68k_options * opts, m68kinst * inst)
{
	code_info *code = &opts->gen.code;
	//best case duration
	cycles(&opts->gen, 10);
	code_ptr skip_loc = NULL;
	//TODO: Check if COND_TRUE technically valid here even though
	//it's basically a slow NOP
	if (inst->extra.cond != COND_FALSE) {
		uint8_t cond = m68k_eval_cond(opts, inst->extra.cond);
		check_alloc_code(code, 6*MAX_INST_LEN);
		skip_loc = code->cur + 1;
		jcc(code, cond, code->cur + 2);
	}
	if (opts->dregs[inst->dst.params.regs.pri] >= 0) {
		sub_ir(code, 1, opts->dregs[inst->dst.params.regs.pri], SZ_W);
		cmp_ir(code, -1, opts->dregs[inst->dst.params.regs.pri], SZ_W);
	} else {
		sub_irdisp(code, 1, opts->gen.context_reg, offsetof(m68k_context, dregs) + 4 * inst->dst.params.regs.pri, SZ_W);
		cmp_irdisp(code, -1, opts->gen.context_reg, offsetof(m68k_context, dregs) + 4 * inst->dst.params.regs.pri, SZ_W);
	}
	code_ptr loop_end_loc = code->cur + 1;
	jcc(code, CC_Z, code->cur + 2);
	uint32_t after = inst->address + 2;
	jump_m68k_abs(opts, after + inst->src.params.immed);
	*loop_end_loc = code->cur - (loop_end_loc+1);
	if (skip_loc) {
		cycles(&opts->gen, 2);
		*skip_loc = code->cur - (skip_loc+1);
		cycles(&opts->gen, 2);
	} else {
		cycles(&opts->gen, 4);
	}
}

void translate_m68k_movep(m68k_options * opts, m68kinst * inst)
{
	code_info *code = &opts->gen.code;
	int8_t reg;
	cycles(&opts->gen, BUS*2);
	if (inst->src.addr_mode == MODE_REG) {
		calc_areg_displace(opts, &inst->dst, opts->gen.scratch2);
		reg = native_reg(&(inst->src), opts);
		if (inst->extra.size == OPSIZE_LONG) {
			if (reg >= 0) {
				mov_rr(code, reg, opts->gen.scratch1, SZ_D);
				shr_ir(code, 24, opts->gen.scratch1, SZ_D);
				push_r(code, opts->gen.scratch2);
				call(code, opts->write_8);
				pop_r(code, opts->gen.scratch2);
				mov_rr(code, reg, opts->gen.scratch1, SZ_D);
				shr_ir(code, 16, opts->gen.scratch1, SZ_D);

			} else {
				mov_rdispr(code, opts->gen.context_reg, reg_offset(&(inst->src))+3, opts->gen.scratch1, SZ_B);
				push_r(code, opts->gen.scratch2);
				call(code, opts->write_8);
				pop_r(code, opts->gen.scratch2);
				mov_rdispr(code, opts->gen.context_reg, reg_offset(&(inst->src))+2, opts->gen.scratch1, SZ_B);
			}
			add_ir(code, 2, opts->gen.scratch2, SZ_D);
			push_r(code, opts->gen.scratch2);
			call(code, opts->write_8);
			pop_r(code, opts->gen.scratch2);
			add_ir(code, 2, opts->gen.scratch2, SZ_D);
		}
		if (reg >= 0) {
			mov_rr(code, reg, opts->gen.scratch1, SZ_W);
			shr_ir(code, 8, opts->gen.scratch1, SZ_W);
			push_r(code, opts->gen.scratch2);
			call(code, opts->write_8);
			pop_r(code, opts->gen.scratch2);
			mov_rr(code, reg, opts->gen.scratch1, SZ_W);
		} else {
			mov_rdispr(code, opts->gen.context_reg, reg_offset(&(inst->src))+1, opts->gen.scratch1, SZ_B);
			push_r(code, opts->gen.scratch2);
			call(code, opts->write_8);
			pop_r(code, opts->gen.scratch2);
			mov_rdispr(code, opts->gen.context_reg, reg_offset(&(inst->src)), opts->gen.scratch1, SZ_B);
		}
		add_ir(code, 2, opts->gen.scratch2, SZ_D);
		call(code, opts->write_8);
	} else {
		calc_areg_displace(opts, &inst->src, opts->gen.scratch1);
		reg = native_reg(&(inst->dst), opts);
		if (inst->extra.size == OPSIZE_LONG) {
			if (reg >= 0) {
				push_r(code, opts->gen.scratch1);
				call(code, opts->read_8);
				shl_ir(code, 24, opts->gen.scratch1, SZ_D);
				mov_rr(code, opts->gen.scratch1, reg, SZ_D);
				pop_r(code, opts->gen.scratch1);
				add_ir(code, 2, opts->gen.scratch1, SZ_D);
				push_r(code, opts->gen.scratch1);
				call(code, opts->read_8);
				shl_ir(code, 16, opts->gen.scratch1, SZ_D);
				or_rr(code, opts->gen.scratch1, reg, SZ_D);
			} else {
				push_r(code, opts->gen.scratch1);
				call(code, opts->read_8);
				mov_rrdisp(code, opts->gen.scratch1, opts->gen.context_reg, reg_offset(&(inst->dst))+3, SZ_B);
				pop_r(code, opts->gen.scratch1);
				add_ir(code, 2, opts->gen.scratch1, SZ_D);
				push_r(code, opts->gen.scratch1);
				call(code, opts->read_8);
				mov_rrdisp(code, opts->gen.scratch1, opts->gen.context_reg, reg_offset(&(inst->dst))+2, SZ_B);
			}
			pop_r(code, opts->gen.scratch1);
			add_ir(code, 2, opts->gen.scratch1, SZ_D);
		}
		push_r(code, opts->gen.scratch1);
		call(code, opts->read_8);
		if (reg >= 0) {

			shl_ir(code, 8, opts->gen.scratch1, SZ_W);
			mov_rr(code, opts->gen.scratch1, reg, SZ_W);
			pop_r(code, opts->gen.scratch1);
			add_ir(code, 2, opts->gen.scratch1, SZ_D);
			call(code, opts->read_8);
			mov_rr(code, opts->gen.scratch1, reg, SZ_B);
		} else {
			mov_rrdisp(code, opts->gen.scratch1, opts->gen.context_reg, reg_offset(&(inst->dst))+1, SZ_B);
			pop_r(code, opts->gen.scratch1);
			add_ir(code, 2, opts->gen.scratch1, SZ_D);
			call(code, opts->read_8);
			mov_rrdisp(code, opts->gen.scratch1, opts->gen.context_reg, reg_offset(&(inst->dst)), SZ_B);
		}
	}
}

typedef void (*shift_ir_t)(code_info *code, uint8_t val, uint8_t dst, uint8_t size);
typedef void (*shift_irdisp_t)(code_info *code, uint8_t val, uint8_t dst_base, int32_t disp, uint8_t size);
typedef void (*shift_clr_t)(code_info *code, uint8_t dst, uint8_t size);
typedef void (*shift_clrdisp_t)(code_info *code, uint8_t dst_base, int32_t disp, uint8_t size);

void translate_shift(m68k_options * opts, m68kinst * inst, x86_ea *src_op, x86_ea * dst_op, shift_ir_t shift_ir, shift_irdisp_t shift_irdisp, shift_clr_t shift_clr, shift_clrdisp_t shift_clrdisp, shift_ir_t special, shift_irdisp_t special_disp)
{
	code_info *code = &opts->gen.code;
	code_ptr end_off = NULL;
	code_ptr nz_off = NULL;
	code_ptr z_off = NULL;
	if (inst->src.addr_mode == MODE_UNUSED) {
		cycles(&opts->gen, BUS);
		//Memory shift
		shift_ir(code, 1, dst_op->base, SZ_W);
	} else {
		cycles(&opts->gen, inst->extra.size == OPSIZE_LONG ? 8 : 6);
		if (src_op->mode == MODE_IMMED) {
			if (src_op->disp != 1 && inst->op == M68K_ASL) {
				set_flag(opts, 0, FLAG_V);
				for (int i = 0; i < src_op->disp; i++) {
					if (dst_op->mode == MODE_REG_DIRECT) {
						shift_ir(code, 1, dst_op->base, inst->extra.size);
					} else {
						shift_irdisp(code, 1, dst_op->base, dst_op->disp, inst->extra.size);
					}
					check_alloc_code(code, 2*MAX_INST_LEN);
					code_ptr after_flag_set = code->cur + 1;
					jcc(code, CC_NO, code->cur + 2);
					set_flag(opts, 1, FLAG_V);
					*after_flag_set = code->cur - (after_flag_set+1);
				}
			} else {
				if (dst_op->mode == MODE_REG_DIRECT) {
					shift_ir(code, src_op->disp, dst_op->base, inst->extra.size);
				} else {
					shift_irdisp(code, src_op->disp, dst_op->base, dst_op->disp, inst->extra.size);
				}
				set_flag_cond(opts, CC_O, FLAG_V);
			}
		} else {
			if (src_op->base != RCX) {
				if (src_op->mode == MODE_REG_DIRECT) {
					mov_rr(code, src_op->base, RCX, SZ_B);
				} else {
					mov_rdispr(code, src_op->base, src_op->disp, RCX, SZ_B);
				}

			}
			and_ir(code, 63, RCX, SZ_D);
			check_alloc_code(code, 7*MAX_INST_LEN);
			nz_off = code->cur + 1;
			jcc(code, CC_NZ, code->cur + 2);
			//Flag behavior for shift count of 0 is different for x86 than 68K
			if (dst_op->mode == MODE_REG_DIRECT) {
				cmp_ir(code, 0, dst_op->base, inst->extra.size);
			} else {
				cmp_irdisp(code, 0, dst_op->base, dst_op->disp, inst->extra.size);
			}
			set_flag_cond(opts, CC_Z, FLAG_Z);
			set_flag_cond(opts, CC_S, FLAG_N);
			set_flag(opts, 0, FLAG_C);
			//For other instructions, this flag will be set below
			if (inst->op == M68K_ASL) {
				set_flag(opts, 0, FLAG_V);
			}
			z_off = code->cur + 1;
			jmp(code, code->cur + 2);
			*nz_off = code->cur - (nz_off + 1);
			//add 2 cycles for every bit shifted
			add_rr(code, RCX, CYCLES, SZ_D);
			add_rr(code, RCX, CYCLES, SZ_D);
			if (inst->op == M68K_ASL) {
				//ASL has Overflow flag behavior that depends on all of the bits shifted through the MSB
				//Easiest way to deal with this is to shift one bit at a time
				set_flag(opts, 0, FLAG_V);
				check_alloc_code(code, 5*MAX_INST_LEN);
				code_ptr loop_start = code->cur;
				if (dst_op->mode == MODE_REG_DIRECT) {
					shift_ir(code, 1, dst_op->base, inst->extra.size);
				} else {
					shift_irdisp(code, 1, dst_op->base, dst_op->disp, inst->extra.size);
				}
				code_ptr after_flag_set = code->cur + 1;
				jcc(code, CC_NO, code->cur + 2);
				set_flag(opts, 1, FLAG_V);
				*after_flag_set = code->cur - (after_flag_set+1);
				loop(code, loop_start);
			} else {
				//x86 shifts modulo 32 for operand sizes less than 64-bits
				//but M68K shifts modulo 64, so we need to check for large shifts here
				cmp_ir(code, 32, RCX, SZ_B);
				check_alloc_code(code, 14*MAX_INST_LEN);
				code_ptr norm_shift_off = code->cur + 1;
				jcc(code, CC_L, code->cur + 2);
				if (special) {
					code_ptr after_flag_set = NULL;
					if (inst->extra.size == OPSIZE_LONG) {
						code_ptr neq_32_off = code->cur + 1;
						jcc(code, CC_NZ, code->cur + 2);

						//set the carry bit to the lsb
						if (dst_op->mode == MODE_REG_DIRECT) {
							special(code, 1, dst_op->base, SZ_D);
						} else {
							special_disp(code, 1, dst_op->base, dst_op->disp, SZ_D);
						}
						set_flag_cond(opts, CC_C, FLAG_C);
						after_flag_set = code->cur + 1;
						jmp(code, code->cur + 2);
						*neq_32_off = code->cur - (neq_32_off+1);
					}
					set_flag(opts, 0, FLAG_C);
					if (after_flag_set) {
						*after_flag_set = code->cur - (after_flag_set+1);
					}
					set_flag(opts, 1, FLAG_Z);
					set_flag(opts, 0, FLAG_N);
					if (dst_op->mode == MODE_REG_DIRECT) {
						xor_rr(code, dst_op->base, dst_op->base, inst->extra.size);
					} else {
						mov_irdisp(code, 0, dst_op->base, dst_op->disp, inst->extra.size);
					}
				} else {
					if (dst_op->mode == MODE_REG_DIRECT) {
						shift_ir(code, 31, dst_op->base, inst->extra.size);
						shift_ir(code, 1, dst_op->base, inst->extra.size);
					} else {
						shift_irdisp(code, 31, dst_op->base, dst_op->disp, inst->extra.size);
						shift_irdisp(code, 1, dst_op->base, dst_op->disp, inst->extra.size);
					}

				}
				end_off = code->cur + 1;
				jmp(code, code->cur + 2);
				*norm_shift_off = code->cur - (norm_shift_off+1);
				if (dst_op->mode == MODE_REG_DIRECT) {
					shift_clr(code, dst_op->base, inst->extra.size);
				} else {
					shift_clrdisp(code, dst_op->base, dst_op->disp, inst->extra.size);
				}
			}
		}

	}
	if (!special && end_off) {
		*end_off = code->cur - (end_off + 1);
	}
	set_flag_cond(opts, CC_C, FLAG_C);
	set_flag_cond(opts, CC_Z, FLAG_Z);
	set_flag_cond(opts, CC_S, FLAG_N);
	if (special && end_off) {
		*end_off = code->cur - (end_off + 1);
	}
	//set X flag to same as C flag
	if (opts->flag_regs[FLAG_C] >= 0) {
		flag_to_flag(opts, FLAG_C, FLAG_X);
	} else {
		set_flag_cond(opts, CC_C, FLAG_X);
	}
	if (z_off) {
		*z_off = code->cur - (z_off + 1);
	}
	if (inst->op != M68K_ASL) {
		set_flag(opts, 0, FLAG_V);
	}
	if (inst->src.addr_mode == MODE_UNUSED) {
		m68k_save_result(inst, opts);
	}
}

void op_ir(code_info *code, m68kinst *inst, int32_t val, uint8_t dst, uint8_t size)
{
	switch (inst->op)
	{
	case M68K_ADD:  add_ir(code, val, dst, size); break;
	case M68K_ADDX: adc_ir(code, val, dst, size); break;
	case M68K_AND:  and_ir(code, val, dst, size); break;
	case M68K_BTST: bt_ir(code, val, dst, size); break;
	case M68K_BSET: bts_ir(code, val, dst, size); break;
	case M68K_BCLR: btr_ir(code, val, dst, size); break;
	case M68K_BCHG: btc_ir(code, val, dst, size); break;
	case M68K_EOR:  xor_ir(code, val, dst, size); break;
	case M68K_OR:   or_ir(code, val, dst, size); break;
	case M68K_ROL:  rol_ir(code, val, dst, size); break;
	case M68K_ROR:  ror_ir(code, val, dst, size); break;
	case M68K_ROXL: rcl_ir(code, val, dst, size); break;
	case M68K_ROXR: rcr_ir(code, val, dst, size); break;
	case M68K_SUB:  sub_ir(code, val, dst, size); break;
	case M68K_SUBX: sbb_ir(code, val, dst, size); break;
	}
}

void op_irdisp(code_info *code, m68kinst *inst, int32_t val, uint8_t dst, int32_t disp, uint8_t size)
{
	switch (inst->op)
	{
	case M68K_ADD:  add_irdisp(code, val, dst, disp, size); break;
	case M68K_ADDX: adc_irdisp(code, val, dst, disp, size); break;
	case M68K_AND:  and_irdisp(code, val, dst, disp, size); break;
	case M68K_BTST: bt_irdisp(code, val, dst, disp, size); break;
	case M68K_BSET: bts_irdisp(code, val, dst, disp, size); break;
	case M68K_BCLR: btr_irdisp(code, val, dst, disp, size); break;
	case M68K_BCHG: btc_irdisp(code, val, dst, disp, size); break;
	case M68K_EOR:  xor_irdisp(code, val, dst, disp, size); break;
	case M68K_OR:   or_irdisp(code, val, dst, disp, size); break;
	case M68K_ROL:  rol_irdisp(code, val, dst, disp, size); break;
	case M68K_ROR:  ror_irdisp(code, val, dst, disp, size); break;
	case M68K_ROXL: rcl_irdisp(code, val, dst, disp, size); break;
	case M68K_ROXR: rcr_irdisp(code, val, dst, disp, size); break;
	case M68K_SUB:  sub_irdisp(code, val, dst, disp, size); break;
	case M68K_SUBX: sbb_irdisp(code, val, dst, disp, size); break;
	}
}

void op_rr(code_info *code, m68kinst *inst, uint8_t src, uint8_t dst, uint8_t size)
{
	switch (inst->op)
	{
	case M68K_ADD:  add_rr(code, src, dst, size); break;
	case M68K_ADDX: adc_rr(code, src, dst, size); break;
	case M68K_AND:  and_rr(code, src, dst, size); break;
	case M68K_BTST: bt_rr(code, src, dst, size); break;
	case M68K_BSET: bts_rr(code, src, dst, size); break;
	case M68K_BCLR: btr_rr(code, src, dst, size); break;
	case M68K_BCHG: btc_rr(code, src, dst, size); break;
	case M68K_EOR:  xor_rr(code, src, dst, size); break;
	case M68K_OR:   or_rr(code, src, dst, size); break;
	case M68K_SUB:  sub_rr(code, src, dst, size); break;
	case M68K_SUBX: sbb_rr(code, src, dst, size); break;
	}
}

void op_rrdisp(code_info *code, m68kinst *inst, uint8_t src, uint8_t dst, int32_t disp, uint8_t size)
{
	switch (inst->op)
	{
	case M68K_ADD:  add_rrdisp(code, src, dst, disp, size); break;
	case M68K_ADDX: adc_rrdisp(code, src, dst, disp, size); break;
	case M68K_AND:  and_rrdisp(code, src, dst, disp, size); break;
	case M68K_BTST: bt_rrdisp(code, src, dst, disp, size); break;
	case M68K_BSET: bts_rrdisp(code, src, dst, disp, size); break;
	case M68K_BCLR: btr_rrdisp(code, src, dst, disp, size); break;
	case M68K_BCHG: btc_rrdisp(code, src, dst, disp, size); break;
	case M68K_EOR:  xor_rrdisp(code, src, dst, disp, size); break;
	case M68K_OR:   or_rrdisp(code, src, dst, disp, size); break;
	case M68K_SUB:  sub_rrdisp(code, src, dst, disp, size); break;
	case M68K_SUBX: sbb_rrdisp(code, src, dst, disp, size); break;
	}
}

void op_rdispr(code_info *code, m68kinst *inst, uint8_t src, int32_t disp, uint8_t dst, uint8_t size)
{
	switch (inst->op)
	{
	case M68K_ADD:  add_rdispr(code, src, disp, dst, size); break;
	case M68K_ADDX: adc_rdispr(code, src, disp, dst, size); break;
	case M68K_AND:  and_rdispr(code, src, disp, dst, size); break;
	case M68K_EOR:  xor_rdispr(code, src, disp, dst, size); break;
	case M68K_OR:   or_rdispr(code, src, disp, dst, size); break;
	case M68K_SUB:  sub_rdispr(code, src, disp, dst, size); break;
	case M68K_SUBX: sbb_rdispr(code, src, disp, dst, size); break;
	}
}

void translate_m68k_arith(m68k_options *opts, m68kinst * inst, uint32_t flag_mask, x86_ea *src_op, x86_ea *dst_op)
{
	code_info *code = &opts->gen.code;
	cycles(&opts->gen, BUS);
	if (inst->op == M68K_ADDX || inst->op == M68K_SUBX) {
		flag_to_carry(opts, FLAG_X);
	}
	uint8_t size = inst->dst.addr_mode == MODE_AREG ? OPSIZE_LONG : inst->extra.size;
	if (src_op->mode == MODE_REG_DIRECT) {
		if (dst_op->mode == MODE_REG_DIRECT) {
			op_rr(code, inst, src_op->base, dst_op->base, size);
		} else {
			op_rrdisp(code, inst, src_op->base, dst_op->base, dst_op->disp, size);
		}
	} else if (src_op->mode == MODE_REG_DISPLACE8) {
		op_rdispr(code, inst, src_op->base, src_op->disp, dst_op->base, size);
	} else {
		if (dst_op->mode == MODE_REG_DIRECT) {
			op_ir(code, inst, src_op->disp, dst_op->base, size);
		} else {
			op_irdisp(code, inst, src_op->disp, dst_op->base, dst_op->disp, size);
		}
	}
	if (inst->dst.addr_mode != MODE_AREG || inst->op == M68K_CMP) {
		update_flags(opts, flag_mask);
		if (inst->op == M68K_ADDX || inst->op == M68K_SUBX) {
			check_alloc_code(code, 2*MAX_INST_LEN);
			code_ptr after_flag_set = code->cur + 1;
			jcc(code, CC_Z, code->cur + 2);
			set_flag(opts, 0, FLAG_Z);
			*after_flag_set = code->cur - (after_flag_set+1);
		}
	}
	if (inst->op != M68K_CMP) {
		m68k_save_result(inst, opts);
	}
}

void translate_m68k_cmp(m68k_options * opts, m68kinst * inst)
{
	code_info *code = &opts->gen.code;
	uint8_t size = inst->extra.size;
	x86_ea src_op, dst_op;
	translate_m68k_op(inst, &src_op, opts, 0);
	if (inst->dst.addr_mode == MODE_AREG_POSTINC) {
		push_r(code, opts->gen.scratch1);
		translate_m68k_op(inst, &dst_op, opts, 1);
		pop_r(code, opts->gen.scratch2);
		src_op.base = opts->gen.scratch2;
	} else {
		translate_m68k_op(inst, &dst_op, opts, 1);
		if (inst->dst.addr_mode == MODE_AREG && size == OPSIZE_WORD) {
			size = OPSIZE_LONG;
		}
	}
	translate_m68k_arith(opts, inst, N|Z|V|C, &src_op, &dst_op);
}

void op_r(code_info *code, m68kinst *inst, uint8_t dst, uint8_t size)
{
	switch(inst->op)
	{
	case M68K_NEG:   neg_r(code, dst, size); break;
	case M68K_NOT:   not_r(code, dst, size); cmp_ir(code, 0, dst, size); break;
	case M68K_ROL:   rol_clr(code, dst, size); break;
	case M68K_ROR:   ror_clr(code, dst, size); break;
	case M68K_ROXL:  rcl_clr(code, dst, size); break;
	case M68K_ROXR:  rcr_clr(code, dst, size); break;
	case M68K_SWAP:  rol_ir(code, 16, dst, SZ_D); cmp_ir(code, 0, dst, SZ_D); break;
	case M68K_TST:   cmp_ir(code, 0, dst, size); break;
	}
}

void op_rdisp(code_info *code, m68kinst *inst, uint8_t dst, int32_t disp, uint8_t size)
{
	switch(inst->op)
	{
	case M68K_NEG:   neg_rdisp(code, dst, disp, size); break;
	case M68K_NOT:   not_rdisp(code, dst, disp, size); cmp_irdisp(code, 0, dst, disp, size); break;
	case M68K_ROL:   rol_clrdisp(code, dst, disp, size); break;
	case M68K_ROR:   ror_clrdisp(code, dst, disp, size); break;
	case M68K_ROXL:  rcl_clrdisp(code, dst, disp, size); break;
	case M68K_ROXR:  rcr_clrdisp(code, dst, disp, size); break;
	case M68K_SWAP:  rol_irdisp(code, 16, dst, disp, SZ_D); cmp_irdisp(code, 0, dst, disp, SZ_D); break;
	case M68K_TST:   cmp_irdisp(code, 0, dst, disp, size); break;
	}
}

void translate_m68k_unary(m68k_options *opts, m68kinst *inst, uint32_t flag_mask, x86_ea *dst_op)
{
	code_info *code = &opts->gen.code;
	cycles(&opts->gen, BUS);
	if (dst_op->mode == MODE_REG_DIRECT) {
		op_r(code, inst, dst_op->base, inst->extra.size);
	} else {
		op_rdisp(code, inst, dst_op->base, dst_op->disp, inst->extra.size);
	}
	update_flags(opts, flag_mask);
	m68k_save_result(inst, opts);
}

#define BIT_SUPERVISOR 5

void translate_m68k(m68k_options * opts, m68kinst * inst)
{
	code_ptr end_off, zero_off, norm_off;
	uint8_t dst_reg;
	code_info *code = &opts->gen.code;
	check_cycles_int(&opts->gen, inst->address);
	if (inst->op == M68K_MOVE) {
		return translate_m68k_move(opts, inst);
	} else if(inst->op == M68K_LEA || inst->op == M68K_PEA) {
		return translate_m68k_lea_pea(opts, inst);
	} else if(inst->op == M68K_BSR) {
		return translate_m68k_bsr(opts, inst);
	} else if(inst->op == M68K_BCC) {
		return translate_m68k_bcc(opts, inst);
	} else if(inst->op == M68K_JMP) {
		return translate_m68k_jmp_jsr(opts, inst);
	} else if(inst->op == M68K_JSR) {
		return translate_m68k_jmp_jsr(opts, inst);
	} else if(inst->op == M68K_RTS) {
		return translate_m68k_rts(opts, inst);
	} else if(inst->op == M68K_DBCC) {
		return translate_m68k_dbcc(opts, inst);
	} else if(inst->op == M68K_CLR) {
		return translate_m68k_clr(opts, inst);
	} else if(inst->op == M68K_MOVEM) {
		return translate_m68k_movem(opts, inst);
	} else if(inst->op == M68K_LINK) {
		return translate_m68k_link(opts, inst);
	} else if(inst->op == M68K_UNLK) {
		return translate_m68k_unlk(opts, inst);
	} else if(inst->op == M68K_EXT) {
		return translate_m68k_ext(opts, inst);
	} else if(inst->op == M68K_SCC) {
		return translate_m68k_scc(opts, inst);
	} else if(inst->op == M68K_MOVEP) {
		return translate_m68k_movep(opts, inst);
	} else if(inst->op == M68K_INVALID) {
		if (inst->src.params.immed == 0x7100) {
			return retn(code);
		}
		mov_ir(code, inst->address, opts->gen.scratch1, SZ_D);
		return call(code, (code_ptr)m68k_invalid);
	} else if(inst->op == M68K_CMP) {
		return translate_m68k_cmp(opts, inst);
	}
	x86_ea src_op, dst_op;
	if (inst->src.addr_mode != MODE_UNUSED) {
		translate_m68k_op(inst, &src_op, opts, 0);
	}
	if (inst->dst.addr_mode != MODE_UNUSED) {
		translate_m68k_op(inst, &dst_op, opts, 1);
	}
	uint8_t size;
	switch(inst->op)
	{
	case M68K_ABCD:
	case M68K_SBCD:
		if (src_op.base != opts->gen.scratch2) {
			if (src_op.mode == MODE_REG_DIRECT) {
				mov_rr(code, src_op.base, opts->gen.scratch2, SZ_B);
			} else {
				mov_rdispr(code, src_op.base, src_op.disp, opts->gen.scratch2, SZ_B);
			}
		}
		if (dst_op.base != opts->gen.scratch1) {
			if (dst_op.mode == MODE_REG_DIRECT) {
				mov_rr(code, dst_op.base, opts->gen.scratch1, SZ_B);
			} else {
				mov_rdispr(code, dst_op.base, dst_op.disp, opts->gen.scratch1, SZ_B);
			}
		}
		flag_to_carry(opts, FLAG_X);
		jcc(code, CC_NC, code->cur + 5);
		if (inst->op == M68K_ABCD) {
			add_ir(code, 1, opts->gen.scratch1, SZ_B);
		} else {
			sub_ir(code, 1, opts->gen.scratch1, SZ_B);
		}
		call(code, (code_ptr) (inst->op == M68K_ABCD ? bcd_add : bcd_sub));
		reg_to_flag(opts, CH, FLAG_C);
		reg_to_flag(opts, CH, FLAG_X);
		cmp_ir(code, 0, opts->gen.scratch1, SZ_B);
		jcc(code, CC_Z, code->cur + 4);
		set_flag(opts, 0, FLAG_Z);
		if (dst_op.base != opts->gen.scratch1) {
			if (dst_op.mode == MODE_REG_DIRECT) {
				mov_rr(code, opts->gen.scratch1, dst_op.base, SZ_B);
			} else {
				mov_rrdisp(code, opts->gen.scratch1, dst_op.base, dst_op.disp, SZ_B);
			}
		}
		m68k_save_result(inst, opts);
		break;
	case M68K_ADD:
	case M68K_SUB:
		translate_m68k_arith(opts, inst, X|N|Z|V|C, &src_op, &dst_op);
		break;
	case M68K_ADDX:
	case M68K_SUBX:
		//z flag is special cased in translate_m68k_arith
		translate_m68k_arith(opts, inst, X|N|V|C, &src_op, &dst_op);
		break;
	case M68K_AND:
	case M68K_EOR:
	case M68K_OR:
		translate_m68k_arith(opts, inst, N|Z|V0|C0, &src_op, &dst_op);
		break;
	case M68K_ANDI_CCR:
	case M68K_ANDI_SR: {
		cycles(&opts->gen, 20);
		//TODO: If ANDI to SR, trap if not in supervisor mode
		uint32_t flag_mask = 0;
		if (!(inst->src.params.immed & 0x1)) {
			flag_mask |= C0;
		}
		if (!(inst->src.params.immed & 0x2)) {
			flag_mask |= V0;
		}
		if (!(inst->src.params.immed & 0x4)) {
			flag_mask |= Z0;
		}
		if (!(inst->src.params.immed & 0x8)) {
			flag_mask |= N0;
		}
		if (!(inst->src.params.immed & 0x10)) {
			flag_mask |= X0;
		}
		update_flags(opts, flag_mask);
		if (inst->op == M68K_ANDI_SR) {
			and_irdisp(code, inst->src.params.immed >> 8, opts->gen.context_reg, offsetof(m68k_context, status), SZ_B);
			if (!((inst->src.params.immed >> 8) & (1 << BIT_SUPERVISOR))) {
				//leave supervisor mode
				swap_ssp_usp(opts);
			}
			if (inst->src.params.immed & 0x700) {
				call(code, opts->do_sync);
			}
		}
		break;
	}
	case M68K_ASL:
	case M68K_LSL:
		translate_shift(opts, inst, &src_op, &dst_op, shl_ir, shl_irdisp, shl_clr, shl_clrdisp, shr_ir, shr_irdisp);
		break;
	case M68K_ASR:
		translate_shift(opts, inst, &src_op, &dst_op, sar_ir, sar_irdisp, sar_clr, sar_clrdisp, NULL, NULL);
		break;
	case M68K_LSR:
		translate_shift(opts, inst, &src_op, &dst_op, shr_ir, shr_irdisp, shr_clr, shr_clrdisp, shl_ir, shl_irdisp);
		break;
	case M68K_BCHG:
	case M68K_BCLR:
	case M68K_BSET:
	case M68K_BTST:
		cycles(&opts->gen, inst->extra.size == OPSIZE_BYTE ? 4 : (
			inst->op == M68K_BTST ? 6 : (inst->op == M68K_BCLR ? 10 : 8))
		);
		if (src_op.mode == MODE_IMMED) {
			if (inst->extra.size == OPSIZE_BYTE) {
				src_op.disp &= 0x7;
			}
			if (dst_op.mode == MODE_REG_DIRECT) {
				op_ir(code, inst, src_op.disp, dst_op.base, inst->extra.size);
			} else {
				op_irdisp(code, inst, src_op.disp, dst_op.base, dst_op.disp, inst->extra.size);
			}
		} else {
			if (src_op.mode == MODE_REG_DISPLACE8 || (inst->dst.addr_mode != MODE_REG && src_op.base != opts->gen.scratch1 && src_op.base != opts->gen.scratch2)) {
				if (dst_op.base == opts->gen.scratch1) {
					push_r(code, opts->gen.scratch2);
					if (src_op.mode == MODE_REG_DIRECT) {
						mov_rr(code, src_op.base, opts->gen.scratch2, SZ_B);
					} else {
						mov_rdispr(code, src_op.base, src_op.disp, opts->gen.scratch2, SZ_B);
					}
					src_op.base = opts->gen.scratch2;
				} else {
					if (src_op.mode == MODE_REG_DIRECT) {
						mov_rr(code, src_op.base, opts->gen.scratch1, SZ_B);
					} else {
						mov_rdispr(code, src_op.base, src_op.disp, opts->gen.scratch1, SZ_B);
					}
					src_op.base = opts->gen.scratch1;
				}
			}
			uint8_t size = inst->extra.size;
			if (dst_op.mode == MODE_REG_DISPLACE8) {
				if (src_op.base != opts->gen.scratch1 && src_op.base != opts->gen.scratch2) {
					if (src_op.mode == MODE_REG_DIRECT) {
						mov_rr(code, src_op.base, opts->gen.scratch1, SZ_D);
					} else {
						mov_rdispr(code, src_op.base, src_op.disp, opts->gen.scratch1, SZ_D);
						src_op.mode = MODE_REG_DIRECT;
					}
					src_op.base = opts->gen.scratch1;
				}
				//b### with register destination is modulo 32
				//x86 with a memory destination isn't modulo anything
				//so use an and here to force the value to be modulo 32
				and_ir(code, 31, opts->gen.scratch1, SZ_D);
			} else if(inst->dst.addr_mode != MODE_REG) {
				//b### with memory destination is modulo 8
				//x86-64 doesn't support 8-bit bit operations
				//so we fake it by forcing the bit number to be modulo 8
				and_ir(code, 7, src_op.base, SZ_D);
				size = SZ_D;
			}
			if (dst_op.mode == MODE_REG_DIRECT) {
				op_rr(code, inst, src_op.base, dst_op.base, size);
			} else {
				op_rrdisp(code, inst, src_op.base, dst_op.base, dst_op.disp, size);
			}
			if (src_op.base == opts->gen.scratch2) {
				pop_r(code, opts->gen.scratch2);
			}
		}
		//x86 sets the carry flag to the value of the bit tested
		//68K sets the zero flag to the complement of the bit tested
		set_flag_cond(opts, CC_NC, FLAG_Z);
		if (inst->op != M68K_BTST) {
			m68k_save_result(inst, opts);
		}
		break;
	case M68K_CHK:
	{
		cycles(&opts->gen, 6);
		if (dst_op.mode == MODE_REG_DIRECT) {
			cmp_ir(code, 0, dst_op.base, inst->extra.size);
		} else {
			cmp_irdisp(code, 0, dst_op.base, dst_op.disp, inst->extra.size);
		}
		uint32_t isize;
		switch(inst->src.addr_mode)
		{
		case MODE_AREG_DISPLACE:
		case MODE_AREG_INDEX_DISP8:
		case MODE_ABSOLUTE_SHORT:
		case MODE_PC_INDEX_DISP8:
		case MODE_PC_DISPLACE:
		case MODE_IMMEDIATE:
			isize = 4;
			break;
		case MODE_ABSOLUTE:
			isize = 6;
			break;
		default:
			isize = 2;
		}
		//make sure we won't start a new chunk in the middle of these branches
		check_alloc_code(code, MAX_INST_LEN * 11);
		code_ptr passed = code->cur + 1;
		jcc(code, CC_GE, code->cur + 2);
		set_flag(opts, 1, FLAG_N);
		mov_ir(code, VECTOR_CHK, opts->gen.scratch2, SZ_D);
		mov_ir(code, inst->address+isize, opts->gen.scratch1, SZ_D);
		jmp(code, opts->trap);
		*passed = code->cur - (passed+1);
		if (dst_op.mode == MODE_REG_DIRECT) {
			if (src_op.mode == MODE_REG_DIRECT) {
				cmp_rr(code, src_op.base, dst_op.base, inst->extra.size);
			} else if(src_op.mode == MODE_REG_DISPLACE8) {
				cmp_rdispr(code, src_op.base, src_op.disp, dst_op.base, inst->extra.size);
			} else {
				cmp_ir(code, src_op.disp, dst_op.base, inst->extra.size);
			}
		} else if(dst_op.mode == MODE_REG_DISPLACE8) {
			if (src_op.mode == MODE_REG_DIRECT) {
				cmp_rrdisp(code, src_op.base, dst_op.base, dst_op.disp, inst->extra.size);
			} else {
				cmp_irdisp(code, src_op.disp, dst_op.base, dst_op.disp, inst->extra.size);
			}
		}
		passed = code->cur + 1;
		jcc(code, CC_LE, code->cur + 2);
		set_flag(opts, 0, FLAG_N);
		mov_ir(code, VECTOR_CHK, opts->gen.scratch2, SZ_D);
		mov_ir(code, inst->address+isize, opts->gen.scratch1, SZ_D);
		jmp(code, opts->trap);
		*passed = code->cur - (passed+1);
		cycles(&opts->gen, 4);
		break;
	}
	case M68K_DIVS:
	case M68K_DIVU:
	{
		check_alloc_code(code, MAX_NATIVE_SIZE);
		//TODO: cycle exact division
		cycles(&opts->gen, inst->op == M68K_DIVS ? 158 : 140);
		set_flag(opts, 0, FLAG_C);
		push_r(code, RDX);
		push_r(code, RAX);
		if (dst_op.mode == MODE_REG_DIRECT) {
			mov_rr(code, dst_op.base, RAX, SZ_D);
		} else {
			mov_rdispr(code, dst_op.base, dst_op.disp, RAX, SZ_D);
		}
		if (src_op.mode == MODE_IMMED) {
			mov_ir(code, (src_op.disp & 0x8000) && inst->op == M68K_DIVS ? src_op.disp | 0xFFFF0000 : src_op.disp, opts->gen.scratch2, SZ_D);
		} else if (src_op.mode == MODE_REG_DIRECT) {
			if (inst->op == M68K_DIVS) {
				movsx_rr(code, src_op.base, opts->gen.scratch2, SZ_W, SZ_D);
			} else {
				movzx_rr(code, src_op.base, opts->gen.scratch2, SZ_W, SZ_D);
			}
		} else if (src_op.mode == MODE_REG_DISPLACE8) {
			if (inst->op == M68K_DIVS) {
				movsx_rdispr(code, src_op.base, src_op.disp, opts->gen.scratch2, SZ_W, SZ_D);
			} else {
				movzx_rdispr(code, src_op.base, src_op.disp, opts->gen.scratch2, SZ_W, SZ_D);
			}
		}
		cmp_ir(code, 0, opts->gen.scratch2, SZ_D);
		check_alloc_code(code, 6*MAX_INST_LEN);
		code_ptr not_zero = code->cur + 1;
		jcc(code, CC_NZ, code->cur + 2);
		pop_r(code, RAX);
		pop_r(code, RDX);
		mov_ir(code, VECTOR_INT_DIV_ZERO, opts->gen.scratch2, SZ_D);
		mov_ir(code, inst->address+2, opts->gen.scratch1, SZ_D);
		jmp(code, opts->trap);
		*not_zero = code->cur - (not_zero+1);
		if (inst->op == M68K_DIVS) {
			cdq(code);
		} else {
			xor_rr(code, RDX, RDX, SZ_D);
		}
		if (inst->op == M68K_DIVS) {
			idiv_r(code, opts->gen.scratch2, SZ_D);
		} else {
			div_r(code, opts->gen.scratch2, SZ_D);
		}
		code_ptr skip_sec_check;
		if (inst->op == M68K_DIVS) {
			cmp_ir(code, 0x8000, RAX, SZ_D);
			skip_sec_check = code->cur + 1;
			jcc(code, CC_GE, code->cur + 2);
			cmp_ir(code, -0x8000, RAX, SZ_D);
			norm_off = code->cur + 1;
			jcc(code, CC_L, code->cur + 2);
		} else {
			cmp_ir(code, 0x10000, RAX, SZ_D);
			norm_off = code->cur + 1;
			jcc(code, CC_NC, code->cur + 2);
		}
		if (dst_op.mode == MODE_REG_DIRECT) {
			mov_rr(code, RDX, dst_op.base, SZ_W);
			shl_ir(code, 16, dst_op.base, SZ_D);
			mov_rr(code, RAX, dst_op.base, SZ_W);
		} else {
			mov_rrdisp(code, RDX, dst_op.base, dst_op.disp, SZ_W);
			shl_irdisp(code, 16, dst_op.base, dst_op.disp, SZ_D);
			mov_rrdisp(code, RAX, dst_op.base, dst_op.disp, SZ_W);
		}
		cmp_ir(code, 0, RAX, SZ_W);
		pop_r(code, RAX);
		pop_r(code, RDX);
		set_flag(opts, 0, FLAG_V);
		set_flag_cond(opts, CC_Z, FLAG_Z);
		set_flag_cond(opts, CC_S, FLAG_N);
		end_off = code->cur + 1;
		jmp(code, code->cur + 2);
		*norm_off = code->cur - (norm_off + 1);
		if (inst->op == M68K_DIVS) {
			*skip_sec_check = code->cur - (skip_sec_check+1);
		}
		pop_r(code, RAX);
		pop_r(code, RDX);
		set_flag(opts, 1, FLAG_V);
		*end_off = code->cur - (end_off + 1);
		break;
	}
	case M68K_EORI_CCR:
	case M68K_EORI_SR:
		cycles(&opts->gen, 20);
		//TODO: If ANDI to SR, trap if not in supervisor mode
		if (inst->src.params.immed & 0x1) {
			xor_flag(opts, 1, FLAG_C);
		}
		if (inst->src.params.immed & 0x2) {
			xor_flag(opts, 1, FLAG_V);
		}
		if (inst->src.params.immed & 0x4) {
			xor_flag(opts, 1, FLAG_Z);
		}
		if (inst->src.params.immed & 0x8) {
			xor_flag(opts, 1, FLAG_N);
		}
		if (inst->src.params.immed & 0x10) {
			xor_flag(opts, 1, FLAG_X);
		}
		if (inst->op == M68K_ORI_SR) {
			xor_irdisp(code, inst->src.params.immed >> 8, opts->gen.context_reg, offsetof(m68k_context, status), SZ_B);
			if (inst->src.params.immed & 0x700) {
				call(code, opts->do_sync);
			}
		}
		break;
	case M68K_EXG:
		cycles(&opts->gen, 6);
		if (dst_op.mode == MODE_REG_DIRECT) {
			mov_rr(code, dst_op.base, opts->gen.scratch2, SZ_D);
			if (src_op.mode == MODE_REG_DIRECT) {
				mov_rr(code, src_op.base, dst_op.base, SZ_D);
				mov_rr(code, opts->gen.scratch2, src_op.base, SZ_D);
			} else {
				mov_rdispr(code, src_op.base, src_op.disp, dst_op.base, SZ_D);
				mov_rrdisp(code, opts->gen.scratch2, src_op.base, src_op.disp, SZ_D);
			}
		} else {
			mov_rdispr(code, dst_op.base, dst_op.disp, opts->gen.scratch2, SZ_D);
			if (src_op.mode == MODE_REG_DIRECT) {
				mov_rrdisp(code, src_op.base, dst_op.base, dst_op.disp, SZ_D);
				mov_rr(code, opts->gen.scratch2, src_op.base, SZ_D);
			} else {
				mov_rdispr(code, src_op.base, src_op.disp, opts->gen.scratch1, SZ_D);
				mov_rrdisp(code, opts->gen.scratch1, dst_op.base, dst_op.disp, SZ_D);
				mov_rrdisp(code, opts->gen.scratch2, src_op.base, src_op.disp, SZ_D);
			}
		}
		break;
	case M68K_ILLEGAL:
		call(code, opts->gen.save_context);
#ifdef X86_64
		mov_rr(code, opts->gen.context_reg, RDI, SZ_PTR);
#else
		push_r(code, opts->gen.context_reg);
#endif
		call(code, (code_ptr)print_regs_exit);
		break;
	case M68K_MOVE_FROM_SR:
		//TODO: Trap if not in system mode
		call(code, opts->get_sr);
		if (dst_op.mode == MODE_REG_DIRECT) {
			mov_rr(code, opts->gen.scratch1, dst_op.base, SZ_W);
		} else {
			mov_rrdisp(code, opts->gen.scratch1, dst_op.base, dst_op.disp, SZ_W);
		}
		m68k_save_result(inst, opts);
		break;
	case M68K_MOVE_CCR:
	case M68K_MOVE_SR:
		//TODO: Privilege check for MOVE to SR
		if (src_op.mode == MODE_IMMED) {
			uint32_t flag_mask = src_op.disp & 0x10 ? X1 : X0;
			flag_mask |= src_op.disp & 0x8 ? N1 : N0;
			flag_mask |= src_op.disp & 0x4 ? Z1 : Z0;
			flag_mask |= src_op.disp & 0x2 ? V1 : V0;
			flag_mask |= src_op.disp & 0x1 ? C1 : C0;
			update_flags(opts, flag_mask);
			if (inst->op == M68K_MOVE_SR) {
				mov_irdisp(code, (src_op.disp >> 8), opts->gen.context_reg, offsetof(m68k_context, status), SZ_B);
				if (!((inst->src.params.immed >> 8) & (1 << BIT_SUPERVISOR))) {
					//leave supervisor mode
					mov_rr(code, opts->aregs[7], opts->gen.scratch1, SZ_D);
					mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, aregs) + sizeof(uint32_t) * 8, opts->aregs[7], SZ_D);
					mov_rrdisp(code, opts->gen.scratch1, opts->gen.context_reg, offsetof(m68k_context, aregs) + sizeof(uint32_t) * 8, SZ_D);
				}
				call(code, opts->do_sync);
			}
			cycles(&opts->gen, 12);
		} else {
			if (src_op.base != opts->gen.scratch1) {
				if (src_op.mode == MODE_REG_DIRECT) {
					mov_rr(code, src_op.base, opts->gen.scratch1, SZ_W);
				} else {
					mov_rdispr(code, src_op.base, src_op.disp, opts->gen.scratch1, SZ_W);
				}
			}
			call(code, inst->op == M68K_MOVE_SR ? opts->set_sr : opts->set_ccr);
			cycles(&opts->gen, 12);

		}
		break;
	case M68K_MOVE_USP:
		cycles(&opts->gen, BUS);
		//TODO: Trap if not in supervisor mode
		//bt_irdisp(code, BIT_SUPERVISOR, opts->gen.context_reg, offsetof(m68k_context, status), SZ_B);
		if (inst->src.addr_mode == MODE_UNUSED) {
			areg_to_native(opts, 8, dst_op.mode == MODE_REG_DIRECT ? dst_op.base : opts->gen.scratch1);
			if (dst_op.mode != MODE_REG_DIRECT) {
				mov_rrdisp(code, opts->gen.scratch1, dst_op.base, dst_op.disp, SZ_D);
			}
		} else {
			if (src_op.mode != MODE_REG_DIRECT) {
				mov_rdispr(code, src_op.base, src_op.disp, opts->gen.scratch1, SZ_D);
				src_op.base = opts->gen.scratch1;
			}
			native_to_areg(opts, src_op.base, 8);
		}
		break;
	//case M68K_MOVEP:
	case M68K_MULS:
	case M68K_MULU:
		cycles(&opts->gen, 70); //TODO: Calculate the actual value based on the value of the <ea> parameter
		if (src_op.mode == MODE_IMMED) {
			mov_ir(code, inst->op == M68K_MULU ? (src_op.disp & 0xFFFF) : ((src_op.disp & 0x8000) ? src_op.disp | 0xFFFF0000 : src_op.disp), opts->gen.scratch1, SZ_D);
		} else if (src_op.mode == MODE_REG_DIRECT) {
			if (inst->op == M68K_MULS) {
				movsx_rr(code, src_op.base, opts->gen.scratch1, SZ_W, SZ_D);
			} else {
				movzx_rr(code, src_op.base, opts->gen.scratch1, SZ_W, SZ_D);
			}
		} else {
			if (inst->op == M68K_MULS) {
				movsx_rdispr(code, src_op.base, src_op.disp, opts->gen.scratch1, SZ_W, SZ_D);
			} else {
				movzx_rdispr(code, src_op.base, src_op.disp, opts->gen.scratch1, SZ_W, SZ_D);
			}
		}
		if (dst_op.mode == MODE_REG_DIRECT) {
			dst_reg = dst_op.base;
			if (inst->op == M68K_MULS) {
				movsx_rr(code, dst_reg, dst_reg, SZ_W, SZ_D);
			} else {
				movzx_rr(code, dst_reg, dst_reg, SZ_W, SZ_D);
			}
		} else {
			dst_reg = opts->gen.scratch2;
			if (inst->op == M68K_MULS) {
				movsx_rdispr(code, dst_op.base, dst_op.disp, opts->gen.scratch2, SZ_W, SZ_D);
			} else {
				movzx_rdispr(code, dst_op.base, dst_op.disp, opts->gen.scratch2, SZ_W, SZ_D);
			}
		}
		imul_rr(code, opts->gen.scratch1, dst_reg, SZ_D);
		if (dst_op.mode == MODE_REG_DISPLACE8) {
			mov_rrdisp(code, dst_reg, dst_op.base, dst_op.disp, SZ_D);
		}
		cmp_ir(code, 0, dst_reg, SZ_D);
		update_flags(opts, N|Z|V0|C0);
		break;
	//case M68K_NBCD:
	case M68K_NEG:
		translate_m68k_unary(opts, inst, X|N|Z|V|C, &dst_op);
		break;
	case M68K_NEGX: {
		cycles(&opts->gen, BUS);
		if (dst_op.mode == MODE_REG_DIRECT) {
			if (dst_op.base == opts->gen.scratch1) {
				push_r(code, opts->gen.scratch2);
				xor_rr(code, opts->gen.scratch2, opts->gen.scratch2, inst->extra.size);
				flag_to_carry(opts, FLAG_X);
				sbb_rr(code, dst_op.base, opts->gen.scratch2, inst->extra.size);
				mov_rr(code, opts->gen.scratch2, dst_op.base, inst->extra.size);
				pop_r(code, opts->gen.scratch2);
			} else {
				xor_rr(code, opts->gen.scratch1, opts->gen.scratch1, inst->extra.size);
				flag_to_carry(opts, FLAG_X);
				sbb_rr(code, dst_op.base, opts->gen.scratch1, inst->extra.size);
				mov_rr(code, opts->gen.scratch1, dst_op.base, inst->extra.size);
			}
		} else {
			xor_rr(code, opts->gen.scratch1, opts->gen.scratch1, inst->extra.size);
			flag_to_carry(opts, FLAG_X);
			sbb_rdispr(code, dst_op.base, dst_op.disp, opts->gen.scratch1, inst->extra.size);
			mov_rrdisp(code, opts->gen.scratch1, dst_op.base, dst_op.disp, inst->extra.size);
		}
		set_flag_cond(opts, CC_C, FLAG_C);
		code_ptr after_flag_set = code->cur + 1;
		jcc(code, CC_Z, code->cur + 2);
		set_flag(opts, 0, FLAG_Z);
		*after_flag_set = code->cur - (after_flag_set+1);
		set_flag_cond(opts, CC_S, FLAG_N);
		set_flag_cond(opts, CC_O, FLAG_V);
		if (opts->flag_regs[FLAG_C] >= 0) {
			flag_to_flag(opts, FLAG_C, FLAG_X);
		} else {
			set_flag_cond(opts, CC_C, FLAG_X);
		}
		m68k_save_result(inst, opts);
		break;
	}
	case M68K_NOP:
		cycles(&opts->gen, BUS);
		break;
	case M68K_NOT:
		translate_m68k_unary(opts, inst, N|Z|V0|C0, &dst_op);
		break;
	case M68K_ORI_CCR:
	case M68K_ORI_SR:
		cycles(&opts->gen, 20);
		//TODO: If ORI to SR, trap if not in supervisor mode
		uint32_t flag_mask = 0;
		if (inst->src.params.immed & 0x1) {
			flag_mask |= C1;
		}
		if (inst->src.params.immed & 0x2) {
			flag_mask |= V1;
		}
		if (inst->src.params.immed & 0x4) {
			flag_mask |= Z1;
		}
		if (inst->src.params.immed & 0x8) {
			flag_mask |= N1;
		}
		if (inst->src.params.immed & 0x10) {
			flag_mask |= X1;
		}
		update_flags(opts, flag_mask);
		if (inst->op == M68K_ORI_SR) {
			or_irdisp(code, inst->src.params.immed >> 8, opts->gen.context_reg, offsetof(m68k_context, status), SZ_B);
			if (inst->src.params.immed & 0x700) {
				call(code, opts->do_sync);
			}
		}
		break;
	case M68K_RESET:
		call(code, opts->gen.save_context);
#ifdef X86_64
		mov_rr(code, opts->gen.context_reg, RDI, SZ_PTR);
#else
		push_r(code, opts->gen.context_reg);
#endif
		call(code, (code_ptr)print_regs_exit);
		break;
	case M68K_ROL:
	case M68K_ROR:
	case M68K_ROXL:
	case M68K_ROXR: {
		int32_t init_flags = C|V0;
		if (inst->src.addr_mode == MODE_UNUSED) {
			cycles(&opts->gen, BUS);
			//Memory rotate
			if (inst->op == M68K_ROXR || inst->op == M68K_ROXL) {
				flag_to_carry(opts, FLAG_X);
				init_flags |= X;
			}
			op_ir(code, inst, 1, dst_op.base, inst->extra.size);
			update_flags(opts, init_flags);
			cmp_ir(code, 0, dst_op.base, inst->extra.size);
			update_flags(opts, Z|N);
			m68k_save_result(inst, opts);
		} else {
			if (src_op.mode == MODE_IMMED) {
				cycles(&opts->gen, (inst->extra.size == OPSIZE_LONG ? 8 : 6) + src_op.disp*2);
				if (inst->op == M68K_ROXR || inst->op == M68K_ROXL) {
					flag_to_carry(opts, FLAG_X);
					init_flags |= X;
				}
				if (dst_op.mode == MODE_REG_DIRECT) {
					op_ir(code, inst, src_op.disp, dst_op.base, inst->extra.size);
				} else {
					op_irdisp(code, inst, src_op.disp, dst_op.base, dst_op.disp, inst->extra.size);
				}
				update_flags(opts, init_flags);
			} else {
				if (src_op.mode == MODE_REG_DIRECT) {
					if (src_op.base != opts->gen.scratch1) {
						mov_rr(code, src_op.base, opts->gen.scratch1, SZ_B);
					}
				} else {
					mov_rdispr(code, src_op.base, src_op.disp, opts->gen.scratch1, SZ_B);
				}
				and_ir(code, 63, opts->gen.scratch1, SZ_D);
				zero_off = code->cur + 1;
				jcc(code, CC_Z, code->cur + 2);
				add_rr(code, opts->gen.scratch1, CYCLES, SZ_D);
				add_rr(code, opts->gen.scratch1, CYCLES, SZ_D);
				cmp_ir(code, 32, opts->gen.scratch1, SZ_B);
				norm_off = code->cur + 1;
				jcc(code, CC_L, code->cur + 2);
				if (inst->op == M68K_ROXR || inst->op == M68K_ROXL) {
					flag_to_carry(opts, FLAG_X);
					init_flags |= X;
				} else {
					sub_ir(code, 32, opts->gen.scratch1, SZ_B);
				}
				if (dst_op.mode == MODE_REG_DIRECT) {
					op_ir(code, inst, 31, dst_op.base, inst->extra.size);
					op_ir(code, inst, 1, dst_op.base, inst->extra.size);
				} else {
					op_irdisp(code, inst, 31, dst_op.base, dst_op.disp, inst->extra.size);
					op_irdisp(code, inst, 1, dst_op.base, dst_op.disp, inst->extra.size);
				}

				if (inst->op == M68K_ROXR || inst->op == M68K_ROXL) {
					set_flag_cond(opts, CC_C, FLAG_X);
					sub_ir(code, 32, opts->gen.scratch1, SZ_B);
					*norm_off = code->cur - (norm_off+1);
					flag_to_carry(opts, FLAG_X);
				} else {
					*norm_off = code->cur - (norm_off+1);
				}
				if (dst_op.mode == MODE_REG_DIRECT) {
					op_r(code, inst, dst_op.base, inst->extra.size);
				} else {
					op_rdisp(code, inst, dst_op.base, dst_op.disp, inst->extra.size);
				}
				update_flags(opts, init_flags);
				end_off = code->cur + 1;
				jmp(code, code->cur + 2);
				*zero_off = code->cur - (zero_off+1);
				if (inst->op == M68K_ROXR || inst->op == M68K_ROXL) {
					//Carry flag is set to X flag when count is 0, this is different from ROR/ROL
					flag_to_flag(opts, FLAG_X, FLAG_C);
				} else {
					set_flag(opts, 0, FLAG_C);
				}
				*end_off = code->cur - (end_off+1);
			}
			if (dst_op.mode == MODE_REG_DIRECT) {
				cmp_ir(code, 0, dst_op.base, inst->extra.size);
			} else {
				cmp_irdisp(code, 0, dst_op.base, dst_op.disp, inst->extra.size);
			}
			update_flags(opts, Z|N);
		}
		break;
	}
	case M68K_RTE:
		//TODO: Trap if not in system mode
		//Read saved SR
		areg_to_native(opts, 7, opts->gen.scratch1);
		call(code, opts->read_16);
		addi_areg(opts, 2, 7);
		call(code, opts->set_sr);
		//Read saved PC
		areg_to_native(opts, 7, opts->gen.scratch1);
		call(code, opts->read_32);
		addi_areg(opts, 4, 7);
		//Check if we've switched to user mode and swap stack pointers if needed
		bt_irdisp(code, 5, opts->gen.context_reg, offsetof(m68k_context, status), SZ_B);
		end_off = code->cur + 1;
		jcc(code, CC_C, code->cur + 2);
		swap_ssp_usp(opts);
		*end_off = code->cur - (end_off+1);
		//Get native address, sync components, recalculate integer points and jump to returned address
		call(code, opts->native_addr_and_sync);
		jmp_r(code, opts->gen.scratch1);
		break;
	case M68K_RTR:
		translate_m68k_rtr(opts, inst);
		break;
	case M68K_STOP: {
		//TODO: Trap if not in system mode
		//manual says 4 cycles, but it has to be at least 8 since it's a 2-word instruction
		//possibly even 12 since that's how long MOVE to SR takes
		cycles(&opts->gen, BUS*2);
		uint32_t flag_mask = src_op.disp & 0x10 ? X1 : X0;
		flag_mask |= src_op.disp & 0x8 ? N1 : N0;
		flag_mask |= src_op.disp & 0x4 ? Z1 : Z0;
		flag_mask |= src_op.disp & 0x2 ? V1 : V0;
		flag_mask |= src_op.disp & 0x1 ? C1 : C0;
		update_flags(opts, flag_mask);
		mov_irdisp(code, (src_op.disp >> 8), opts->gen.context_reg, offsetof(m68k_context, status), SZ_B);
		if (!((inst->src.params.immed >> 8) & (1 << BIT_SUPERVISOR))) {
			//leave supervisor mode
			swap_ssp_usp(opts);
		}
		code_ptr loop_top = code->cur;
		call(code, opts->do_sync);
		cmp_rr(code, opts->gen.limit, opts->gen.cycles, SZ_D);
		code_ptr normal_cycle_up = code->cur + 1;
		jcc(code, CC_A, code->cur + 2);
		cycles(&opts->gen, BUS);
		code_ptr after_cycle_up = code->cur + 1;
		jmp(code, code->cur + 2);
		*normal_cycle_up = code->cur - (normal_cycle_up + 1);
		mov_rr(code, opts->gen.limit, opts->gen.cycles, SZ_D);
		*after_cycle_up = code->cur - (after_cycle_up+1);
		cmp_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, int_cycle), opts->gen.cycles, SZ_D);
		jcc(code, CC_C, loop_top);
		break;
	}
	//case M68K_TAS:
	case M68K_TRAP:
		translate_m68k_trap(opts, inst);
		break;
	//case M68K_TRAPV:
	case M68K_TST:
	case M68K_SWAP:
		translate_m68k_unary(opts, inst, N|Z|V0|C0, &src_op);
		break;
	default:
		m68k_disasm(inst, disasm_buf);
		printf("%X: %s\ninstruction %d not yet implemented\n", inst->address, disasm_buf, inst->op);
		exit(1);
	}
}

void translate_out_of_bounds(code_info *code)
{
	xor_rr(code, RDI, RDI, SZ_D);
#ifdef X86_32
	push_r(code, RDI);
#endif
	call(code, (code_ptr)exit);
}

void check_code_prologue(code_info *code)
{
	check_alloc_code(code, MAX_INST_LEN*4);
}

void * m68k_retranslate_inst(uint32_t address, m68k_context * context)
{
	m68k_options * opts = context->options;
	code_info *code = &opts->gen.code;
	uint8_t orig_size = get_native_inst_size(opts, address);
	code_ptr orig_start = get_native_address(context->native_code_map, address);
	uint32_t orig = address;
	code_info orig_code;
	orig_code.cur = orig_start;
	orig_code.last = orig_start + orig_size + 5;
	address &= 0xFFFF;
	uint16_t *after, *inst = context->mem_pointers[1] + address/2;
	m68kinst instbuf;
	after = m68k_decode(inst, &instbuf, orig);
	if (orig_size != MAX_NATIVE_SIZE) {
		deferred_addr * orig_deferred = opts->gen.deferred;

		//make sure the beginning of the code for an instruction is contiguous
		check_alloc_code(code, MAX_INST_LEN*4);
		code_ptr native_start = code->cur;
		translate_m68k(opts, &instbuf);
		code_ptr native_end = code->cur;
		uint8_t is_terminal = m68k_is_terminal(&instbuf);
		if ((native_end - native_start) <= orig_size) {
			code_ptr native_next;
			if (!is_terminal) {
				native_next = get_native_address(context->native_code_map, orig + (after-inst)*2);
			}
			if (is_terminal || (native_next && ((native_next == orig_start + orig_size) || (orig_size - (native_end - native_start)) > 5))) {
				remove_deferred_until(&opts->gen.deferred, orig_deferred);
				code_info tmp;
				tmp.cur = code->cur;
				tmp.last = code->last;
				code->cur = orig_code.cur;
				code->last = orig_code.last;
				translate_m68k(opts, &instbuf);
				native_end = orig_code.cur = code->cur;
				code->cur = tmp.cur;
				code->last = tmp.last;
				if (!is_terminal) {
					if (native_next == orig_start + orig_size && (native_next-native_end) < 2) {
						while (orig_code.cur < orig_start + orig_size) {
							*(orig_code.cur++) = 0x90; //NOP
						}
					} else {
						jmp(&orig_code, native_next);
					}
				}
				m68k_handle_deferred(context);
				return orig_start;
			}
		}

		map_native_address(context, instbuf.address, native_start, (after-inst)*2, MAX_NATIVE_SIZE);

		jmp(&orig_code, native_start);
		if (!m68k_is_terminal(&instbuf)) {
			code_ptr native_end = code->cur;
			code->cur = native_start + MAX_NATIVE_SIZE;
			code_ptr rest = get_native_address_trans(context, orig + (after-inst)*2);
			code_ptr tmp = code->cur;
			code->cur = native_end;
			jmp(code, rest);
			code->cur = tmp;
		} else {
			code->cur = native_start + MAX_NATIVE_SIZE;
		}
		m68k_handle_deferred(context);
		return native_start;
	} else {
		code_info tmp;
		tmp.cur = code->cur;
		tmp.last = code->last;
		code->cur = orig_code.cur;
		code->last = orig_code.last;
		translate_m68k(opts, &instbuf);
		if (!m68k_is_terminal(&instbuf)) {
			jmp(code, get_native_address_trans(context, orig + (after-inst)*2));
		}
		code->cur = tmp.cur;
		code->last = tmp.last;
		m68k_handle_deferred(context);
		return orig_start;
	}
}

m68k_context * m68k_handle_code_write(uint32_t address, m68k_context * context)
{
	uint32_t inst_start = get_instruction_start(context->native_code_map, address | 0xFF0000);
	if (inst_start) {
		m68k_options * options = context->options;
		code_info *code = &options->gen.code;
		code_ptr dst = get_native_address(context->native_code_map, inst_start);
		code_info orig;
		orig.cur = dst;
		orig.last = dst + 128;
		mov_ir(&orig, inst_start, options->gen.scratch2, SZ_D);

		if (!options->retrans_stub) {
			options->retrans_stub = code->cur;
			call(code, options->gen.save_context);
			push_r(code, options->gen.context_reg);
#ifdef X86_32
			push_r(code, options->gen.context_reg);
			push_r(code, options->gen.scratch2);
#endif
			call(code, (code_ptr)m68k_retranslate_inst);
#ifdef X86_32
			add_ir(code, 8, RSP, SZ_D);
#endif
			pop_r(code, options->gen.context_reg);
			mov_rr(code, RAX, options->gen.scratch1, SZ_PTR);
			call(code, options->gen.load_context);
			jmp_r(code, options->gen.scratch1);
		}
		jmp(&orig, options->retrans_stub);
	}
	return context;
}

void insert_breakpoint(m68k_context * context, uint32_t address, code_ptr bp_handler)
{
	static code_ptr bp_stub = NULL;
	m68k_options * opts = context->options;
	code_info native;
	native.cur = get_native_address_trans(context, address);
	native.last = native.cur + 128;
	code_ptr start_native = native.cur;
	mov_ir(&native, address, opts->gen.scratch1, SZ_D);
	if (!bp_stub) {
		code_info *code = &opts->gen.code;
		check_alloc_code(code, 5);
		bp_stub = code->cur;
		call(&native, bp_stub);

		//Calculate length of prologue
		check_cycles_int(&opts->gen, address);
		int check_int_size = code->cur-bp_stub;
		code->cur = bp_stub;

		//Save context and call breakpoint handler
		call(code, opts->gen.save_context);
		push_r(code, opts->gen.scratch1);
#ifdef X86_64
		mov_rr(code, opts->gen.context_reg, RDI, SZ_PTR);
		mov_rr(code, opts->gen.scratch1, RSI, SZ_D);
#else
		push_r(code, opts->gen.scratch1);
		push_r(code, opts->gen.context_reg);
#endif
		call(code, bp_handler);
#ifdef X86_32
		add_ir(code, 8, RSP, SZ_D);
#endif
		mov_rr(code, RAX, opts->gen.context_reg, SZ_PTR);
		//Restore context
		call(code, opts->gen.load_context);
		pop_r(code, opts->gen.scratch1);
		//do prologue stuff
		cmp_rr(code, opts->gen.cycles, opts->gen.limit, SZ_D);
		code_ptr jmp_off = code->cur + 1;
		jcc(code, CC_NC, code->cur + 7);
		call(code, opts->gen.handle_cycle_limit_int);
		*jmp_off = code->cur - (jmp_off+1);
		//jump back to body of translated instruction
		pop_r(code, opts->gen.scratch1);
		add_ir(code, check_int_size - (native.cur-start_native), opts->gen.scratch1, SZ_PTR);
		jmp_r(code, opts->gen.scratch1);
	} else {
		call(&native, bp_stub);
	}
}

code_ptr gen_mem_fun(cpu_options * opts, memmap_chunk * memmap, uint32_t num_chunks, ftype fun_type)
{
	code_info *code = &opts->code;
	code_ptr start = code->cur;
	check_cycles(opts);
	cycles(opts, BUS);
	and_ir(code, 0xFFFFFF, opts->scratch1, SZ_D);
	code_ptr lb_jcc = NULL, ub_jcc = NULL;
	uint8_t is_write = fun_type == WRITE_16 || fun_type == WRITE_8;
	uint8_t adr_reg = is_write ? opts->scratch2 : opts->scratch1;
	uint16_t access_flag = is_write ? MMAP_WRITE : MMAP_READ;
	uint8_t size =  (fun_type == READ_16 || fun_type == WRITE_16) ? SZ_W : SZ_B;
	for (uint32_t chunk = 0; chunk < num_chunks; chunk++)
	{
		if (memmap[chunk].start > 0) {
			cmp_ir(code, memmap[chunk].start, adr_reg, SZ_D);
			lb_jcc = code->cur + 1;
			jcc(code, CC_C, code->cur + 2);
		}
		if (memmap[chunk].end < 0x1000000) {
			cmp_ir(code, memmap[chunk].end, adr_reg, SZ_D);
			ub_jcc = code->cur + 1;
			jcc(code, CC_NC, code->cur + 2);
		}

		if (memmap[chunk].mask != 0xFFFFFF) {
			and_ir(code, memmap[chunk].mask, adr_reg, SZ_D);
		}
		void * cfun;
		switch (fun_type)
		{
		case READ_16:
			cfun = memmap[chunk].read_16;
			break;
		case READ_8:
			cfun = memmap[chunk].read_8;
			break;
		case WRITE_16:
			cfun = memmap[chunk].write_16;
			break;
		case WRITE_8:
			cfun = memmap[chunk].write_8;
			break;
		default:
			cfun = NULL;
		}
		if(memmap[chunk].buffer && memmap[chunk].flags & access_flag) {
			if (memmap[chunk].flags & MMAP_PTR_IDX) {
				if (memmap[chunk].flags & MMAP_FUNC_NULL) {
					cmp_irdisp(code, 0, opts->context_reg, offsetof(m68k_context, mem_pointers) + sizeof(void*) * memmap[chunk].ptr_index, SZ_PTR);
					code_ptr not_null = code->cur + 1;
					jcc(code, CC_NZ, code->cur + 2);
					call(code, opts->save_context);
#ifdef X86_64
					if (is_write) {
						if (opts->scratch2 != RDI) {
							mov_rr(code, opts->scratch2, RDI, SZ_D);
						}
						mov_rr(code, opts->scratch1, RDX, size);
					} else {
						push_r(code, opts->context_reg);
						mov_rr(code, opts->scratch1, RDI, SZ_D);
					}
					test_ir(code, 8, RSP, SZ_D);
					code_ptr adjust_rsp = code->cur + 1;
					jcc(code, CC_NZ, code->cur + 2);
					call(code, cfun);
					code_ptr no_adjust = code->cur + 1;
					jmp(code, code->cur + 2);
					*adjust_rsp = code->cur - (adjust_rsp + 1);
					sub_ir(code, 8, RSP, SZ_PTR);
					call(code, cfun);
					add_ir(code, 8, RSP, SZ_PTR);
					*no_adjust = code->cur - (no_adjust + 1);
#else
					if (is_write) {
						push_r(code, opts->scratch1);
					} else {
						push_r(code, opts->context_reg);//save opts->context_reg for later
					}
					push_r(code, opts->context_reg);
					push_r(code, is_write ? opts->scratch2 : opts->scratch1);
					call(code, cfun);
					add_ir(code, is_write ? 12 : 8, RSP, SZ_D);
#endif
					if (is_write) {
						mov_rr(code, RAX, opts->context_reg, SZ_PTR);
					} else {
						pop_r(code, opts->context_reg);
						mov_rr(code, RAX, opts->scratch1, size);
					}
					jmp(code, opts->load_context);

					*not_null = code->cur - (not_null + 1);
				}
				if (size == SZ_B) {
					xor_ir(code, 1, adr_reg, SZ_D);
				}
				add_rdispr(code, opts->context_reg, offsetof(m68k_context, mem_pointers) + sizeof(void*) * memmap[chunk].ptr_index, adr_reg, SZ_PTR);
				if (is_write) {
					mov_rrind(code, opts->scratch1, opts->scratch2, size);

				} else {
					mov_rindr(code, opts->scratch1, opts->scratch1, size);
				}
			} else {
				uint8_t tmp_size = size;
				if (size == SZ_B) {
					if ((memmap[chunk].flags & MMAP_ONLY_ODD) || (memmap[chunk].flags & MMAP_ONLY_EVEN)) {
						bt_ir(code, 0, adr_reg, SZ_D);
						code_ptr good_addr = code->cur + 1;
						jcc(code, (memmap[chunk].flags & MMAP_ONLY_ODD) ? CC_C : CC_NC, code->cur + 2);
						if (!is_write) {
							mov_ir(code, 0xFF, opts->scratch1, SZ_B);
						}
						retn(code);
						*good_addr = code->cur - (good_addr + 1);
						shr_ir(code, 1, adr_reg, SZ_D);
					} else {
						xor_ir(code, 1, adr_reg, SZ_D);
					}
				} else if ((memmap[chunk].flags & MMAP_ONLY_ODD) || (memmap[chunk].flags & MMAP_ONLY_EVEN)) {
					tmp_size = SZ_B;
					shr_ir(code, 1, adr_reg, SZ_D);
					if ((memmap[chunk].flags & MMAP_ONLY_EVEN) && is_write) {
						shr_ir(code, 8, opts->scratch1, SZ_W);
					}
				}
				if ((intptr_t)memmap[chunk].buffer <= 0x7FFFFFFF && (intptr_t)memmap[chunk].buffer >= -2147483648) {
					if (is_write) {
						mov_rrdisp(code, opts->scratch1, opts->scratch2, (intptr_t)memmap[chunk].buffer, tmp_size);
					} else {
						mov_rdispr(code, opts->scratch1, (intptr_t)memmap[chunk].buffer, opts->scratch1, tmp_size);
					}
				} else {
					if (is_write) {
						push_r(code, opts->scratch1);
						mov_ir(code, (intptr_t)memmap[chunk].buffer, opts->scratch1, SZ_PTR);
						add_rr(code, opts->scratch1, opts->scratch2, SZ_PTR);
						pop_r(code, opts->scratch1);
						mov_rrind(code, opts->scratch1, opts->scratch2, tmp_size);
					} else {
						mov_ir(code, (intptr_t)memmap[chunk].buffer, opts->scratch2, SZ_PTR);
						mov_rindexr(code, opts->scratch2, opts->scratch1, 1, opts->scratch1, tmp_size);
					}
				}
				if (size != tmp_size && !is_write) {
					if (memmap[chunk].flags & MMAP_ONLY_EVEN) {
						shl_ir(code, 8, opts->scratch1, SZ_W);
						mov_ir(code, 0xFF, opts->scratch1, SZ_B);
					} else {
						or_ir(code, 0xFF00, opts->scratch1, SZ_W);
					}
				}
			}
			if (is_write && (memmap[chunk].flags & MMAP_CODE)) {
				mov_rr(code, opts->scratch2, opts->scratch1, SZ_D);
				shr_ir(code, 11, opts->scratch1, SZ_D);
				bt_rrdisp(code, opts->scratch1, opts->context_reg, offsetof(m68k_context, ram_code_flags), SZ_D);
				code_ptr not_code = code->cur + 1;
				jcc(code, CC_NC, code->cur + 2);
				call(code, opts->save_context);
#ifdef X86_32
				push_r(code, opts->context_reg);
				push_r(code, opts->scratch2);
#endif
				call(code, (code_ptr)m68k_handle_code_write);
#ifdef X86_32
				add_ir(code, 8, RSP, SZ_D);
#endif
				mov_rr(code, RAX, opts->context_reg, SZ_PTR);
				call(code, opts->load_context);
				*not_code = code->cur - (not_code+1);
			}
			retn(code);
		} else if (cfun) {
			call(code, opts->save_context);
#ifdef X86_64
			if (is_write) {
				if (opts->scratch2 != RDI) {
					mov_rr(code, opts->scratch2, RDI, SZ_D);
				}
				mov_rr(code, opts->scratch1, RDX, size);
			} else {
				push_r(code, opts->context_reg);
				mov_rr(code, opts->scratch1, RDI, SZ_D);
			}
			test_ir(code, 8, RSP, SZ_D);
			code_ptr adjust_rsp = code->cur + 1;
			jcc(code, CC_NZ, code->cur + 2);
			call(code, cfun);
			code_ptr no_adjust = code->cur + 1;
			jmp(code, code->cur + 2);
			*adjust_rsp = code->cur - (adjust_rsp + 1);
			sub_ir(code, 8, RSP, SZ_PTR);
			call(code, cfun);
			add_ir(code, 8, RSP, SZ_PTR);
			*no_adjust = code->cur - (no_adjust+1);
#else
			if (is_write) {
				push_r(code, opts->scratch1);
			} else {
				push_r(code, opts->context_reg);//save opts->context_reg for later
			}
			push_r(code, opts->context_reg);
			push_r(code, is_write ? opts->scratch2 : opts->scratch1);
			call(code, cfun);
			add_ir(code, is_write ? 12 : 8, RSP, SZ_D);
#endif
			if (is_write) {
				mov_rr(code, RAX, opts->context_reg, SZ_PTR);
			} else {
				pop_r(code, opts->context_reg);
				mov_rr(code, RAX, opts->scratch1, size);
			}
			jmp(code, opts->load_context);
		} else {
			//Not sure the best course of action here
			if (!is_write) {
				mov_ir(code, size == SZ_B ? 0xFF : 0xFFFF, opts->scratch1, size);
			}
			retn(code);
		}
		if (lb_jcc) {
			*lb_jcc = code->cur - (lb_jcc+1);
			lb_jcc = NULL;
		}
		if (ub_jcc) {
			*ub_jcc = code->cur - (ub_jcc+1);
			ub_jcc = NULL;
		}
	}
	if (!is_write) {
		mov_ir(code, size == SZ_B ? 0xFF : 0xFFFF, opts->scratch1, size);
	}
	retn(code);
	return start;
}

void init_m68k_opts(m68k_options * opts, memmap_chunk * memmap, uint32_t num_chunks)
{
	memset(opts, 0, sizeof(*opts));
	for (int i = 0; i < 8; i++)
	{
		opts->dregs[i] = opts->aregs[i] = -1;
	}
#ifdef X86_64
	opts->dregs[0] = R10;
	opts->dregs[1] = R11;
	opts->dregs[2] = R12;
	opts->dregs[3] = R8;
	opts->aregs[0] = R13;
	opts->aregs[1] = R14;
	opts->aregs[2] = R9;
	opts->aregs[7] = R15;

	opts->flag_regs[0] = -1;
	opts->flag_regs[1] = RBX;
	opts->flag_regs[2] = RDX;
	opts->flag_regs[3] = BH;
	opts->flag_regs[4] = DH;

	opts->gen.scratch2 = RDI;
#else
	opts->dregs[0] = RDX;
	opts->aregs[7] = RDI;

	for (int i = 0; i < 5; i++)
	{
		opts->flag_regs[i] = -1;
	}
	opts->gen.scratch2 = RBX;
#endif
	opts->gen.context_reg = RSI;
	opts->gen.cycles = RAX;
	opts->gen.limit = RBP;
	opts->gen.scratch1 = RCX;


	opts->gen.native_code_map = malloc(sizeof(native_map_slot) * NATIVE_MAP_CHUNKS);
	memset(opts->gen.native_code_map, 0, sizeof(native_map_slot) * NATIVE_MAP_CHUNKS);
	opts->gen.deferred = NULL;
	opts->gen.ram_inst_sizes = malloc(sizeof(uint8_t *) * 64);
	memset(opts->gen.ram_inst_sizes, 0, sizeof(uint8_t *) * 64);

	code_info *code = &opts->gen.code;
	init_code_info(code);

	opts->gen.save_context = code->cur;
	for (int i = 0; i < 5; i++)
		if (opts->flag_regs[i] >= 0) {
			mov_rrdisp(code, opts->flag_regs[i], opts->gen.context_reg, offsetof(m68k_context, flags) + i, SZ_B);
		}
	for (int i = 0; i < 8; i++)
	{
		if (opts->dregs[i] >= 0) {
			mov_rrdisp(code, opts->dregs[i], opts->gen.context_reg, offsetof(m68k_context, dregs) + sizeof(uint32_t) * i, SZ_D);
		}
		if (opts->aregs[i] >= 0) {
			mov_rrdisp(code, opts->aregs[i], opts->gen.context_reg, offsetof(m68k_context, aregs) + sizeof(uint32_t) * i, SZ_D);
		}
	}
	mov_rrdisp(code, opts->gen.cycles, opts->gen.context_reg, offsetof(m68k_context, current_cycle), SZ_D);
	retn(code);

	opts->gen.load_context = code->cur;
	for (int i = 0; i < 5; i++)
		if (opts->flag_regs[i] >= 0) {
			mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, flags) + i, opts->flag_regs[i], SZ_B);
		}
	for (int i = 0; i < 8; i++)
	{
		if (opts->dregs[i] >= 0) {
			mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, dregs) + sizeof(uint32_t) * i, opts->dregs[i], SZ_D);
		}
		if (opts->aregs[i] >= 0) {
			mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, aregs) + sizeof(uint32_t) * i, opts->aregs[i], SZ_D);
		}
	}
	mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, current_cycle), CYCLES, SZ_D);
	mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, target_cycle), LIMIT, SZ_D);
	retn(code);

	opts->start_context = (start_fun)code->cur;
#ifdef X86_64
	if (opts->gen.scratch2 != RDI) {
		mov_rr(code, RDI, opts->gen.scratch2, SZ_PTR);
	}
	//save callee save registers
	push_r(code, RBP);
	push_r(code, R12);
	push_r(code, R13);
	push_r(code, R14);
	push_r(code, R15);
#else
	//save callee save registers
	push_r(code, RBP);
	push_r(code, RBX);
	push_r(code, RSI);
	push_r(code, RDI);

	mov_rdispr(code, RSP, 20, opts->gen.scratch2, SZ_D);
	mov_rdispr(code, RSP, 24, opts->gen.context_reg, SZ_D);
#endif
	call(code, opts->gen.load_context);
	call_r(code, opts->gen.scratch2);
	call(code, opts->gen.save_context);
#ifdef X86_64
	//restore callee save registers
	pop_r(code, R15);
	pop_r(code, R14);
	pop_r(code, R13);
	pop_r(code, R12);
	pop_r(code, RBP);
#else
	pop_r(code, RDI);
	pop_r(code, RSI);
	pop_r(code, RBX);
	pop_r(code, RBP);
#endif
	retn(code);

	opts->native_addr = code->cur;
	call(code, opts->gen.save_context);
	push_r(code, opts->gen.context_reg);
#ifdef X86_64
	mov_rr(code, opts->gen.context_reg, RDI, SZ_PTR); //move context to 1st arg reg
	mov_rr(code, opts->gen.scratch1, RSI, SZ_D); //move address to 2nd arg reg
#else
	push_r(code, opts->gen.scratch1);
	push_r(code, opts->gen.context_reg);
#endif
	call(code, (code_ptr)get_native_address_trans);
#ifdef X86_32
	add_ir(code, 8, RSP, SZ_D);
#endif
	mov_rr(code, RAX, opts->gen.scratch1, SZ_PTR); //move result to scratch reg
	pop_r(code, opts->gen.context_reg);
	call(code, opts->gen.load_context);
	retn(code);

	opts->native_addr_and_sync = code->cur;
	call(code, opts->gen.save_context);
	push_r(code, opts->gen.scratch1);
#ifdef X86_64
	mov_rr(code, opts->gen.context_reg, RDI, SZ_PTR);
	xor_rr(code, RSI, RSI, SZ_D);
	test_ir(code, 8, RSP, SZ_PTR); //check stack alignment
	code_ptr do_adjust_rsp = code->cur + 1;
	jcc(code, CC_NZ, code->cur + 2);
	call(code, (code_ptr)sync_components);
	code_ptr no_adjust_rsp = code->cur + 1;
	jmp(code, code->cur + 2);
	*do_adjust_rsp = code->cur - (do_adjust_rsp+1);
	sub_ir(code, 8, RSP, SZ_PTR);
	call(code, (code_ptr)sync_components);
	add_ir(code, 8, RSP, SZ_PTR);
	*no_adjust_rsp = code->cur - (no_adjust_rsp+1);
	pop_r(code, RSI);
	push_r(code, RAX);
	mov_rr(code, RAX, RDI, SZ_PTR);
	call(code, (code_ptr)get_native_address_trans);
#else
	//TODO: Add support for pushing a constant in gen_x86
	xor_rr(code, RAX, RAX, SZ_D);
	push_r(code, RAX);
	push_r(code, opts->gen.context_reg);
	call(code, (code_ptr)sync_components);
	add_ir(code, 8, RSP, SZ_D);
	pop_r(code, RSI); //restore saved address from opts->gen.scratch1
	push_r(code, RAX); //save context pointer for later
	push_r(code, RSI); //2nd arg -- address
	push_r(code, RAX); //1st arg -- context pointer
	call(code, (code_ptr)get_native_address_trans);
	add_ir(code, 8, RSP, SZ_D);
#endif

	mov_rr(code, RAX, opts->gen.scratch1, SZ_PTR); //move result to scratch reg
	pop_r(code, opts->gen.context_reg);
	call(code, opts->gen.load_context);
	retn(code);

	opts->gen.handle_cycle_limit = code->cur;
	cmp_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, sync_cycle), CYCLES, SZ_D);
	code_ptr skip_sync = code->cur + 1;
	jcc(code, CC_C, code->cur + 2);
	opts->do_sync = code->cur;
	push_r(code, opts->gen.scratch1);
	push_r(code, opts->gen.scratch2);
	call(code, opts->gen.save_context);
#ifdef X86_64
	mov_rr(code, opts->gen.context_reg, RDI, SZ_PTR);
	xor_rr(code, RSI, RSI, SZ_D);
	test_ir(code, 8, RSP, SZ_D);
	code_ptr adjust_rsp = code->cur + 1;
	jcc(code, CC_NZ, code->cur + 2);
	call(code, (code_ptr)sync_components);
	code_ptr no_adjust = code->cur + 1;
	jmp(code, code->cur + 2);
	*adjust_rsp = code->cur - (adjust_rsp + 1);
	sub_ir(code, 8, RSP, SZ_PTR);
	call(code, (code_ptr)sync_components);
	add_ir(code, 8, RSP, SZ_PTR);
	*no_adjust = code->cur - (no_adjust+1);
#else
	//TODO: Add support for pushing a constant in gen_x86
	xor_rr(code, RAX, RAX, SZ_D);
	push_r(code, RAX);
	push_r(code, opts->gen.context_reg);
	call(code, (code_ptr)sync_components);
	add_ir(code, 8, RSP, SZ_D);
#endif
	mov_rr(code, RAX, opts->gen.context_reg, SZ_PTR);
	call(code, opts->gen.load_context);
	pop_r(code, opts->gen.scratch2);
	pop_r(code, opts->gen.scratch1);
	*skip_sync = code->cur - (skip_sync+1);
	retn(code);

	opts->read_16 = gen_mem_fun(&opts->gen, memmap, num_chunks, READ_16);
	opts->read_8 = gen_mem_fun(&opts->gen, memmap, num_chunks, READ_8);
	opts->write_16 = gen_mem_fun(&opts->gen, memmap, num_chunks, WRITE_16);
	opts->write_8 = gen_mem_fun(&opts->gen, memmap, num_chunks, WRITE_8);

	opts->read_32 = code->cur;
	push_r(code, opts->gen.scratch1);
	call(code, opts->read_16);
	mov_rr(code, opts->gen.scratch1, opts->gen.scratch2, SZ_W);
	pop_r(code, opts->gen.scratch1);
	push_r(code, opts->gen.scratch2);
	add_ir(code, 2, opts->gen.scratch1, SZ_D);
	call(code, opts->read_16);
	pop_r(code, opts->gen.scratch2);
	movzx_rr(code, opts->gen.scratch1, opts->gen.scratch1, SZ_W, SZ_D);
	shl_ir(code, 16, opts->gen.scratch2, SZ_D);
	or_rr(code, opts->gen.scratch2, opts->gen.scratch1, SZ_D);
	retn(code);

	opts->write_32_lowfirst = code->cur;
	push_r(code, opts->gen.scratch2);
	push_r(code, opts->gen.scratch1);
	add_ir(code, 2, opts->gen.scratch2, SZ_D);
	call(code, opts->write_16);
	pop_r(code, opts->gen.scratch1);
	pop_r(code, opts->gen.scratch2);
	shr_ir(code, 16, opts->gen.scratch1, SZ_D);
	jmp(code, opts->write_16);

	opts->write_32_highfirst = code->cur;
	push_r(code, opts->gen.scratch1);
	push_r(code, opts->gen.scratch2);
	shr_ir(code, 16, opts->gen.scratch1, SZ_D);
	call(code, opts->write_16);
	pop_r(code, opts->gen.scratch2);
	pop_r(code, opts->gen.scratch1);
	add_ir(code, 2, opts->gen.scratch2, SZ_D);
	jmp(code, opts->write_16);

	opts->get_sr = code->cur;
	mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, status), opts->gen.scratch1, SZ_B);
	shl_ir(code, 8, opts->gen.scratch1, SZ_W);
	if (opts->flag_regs[FLAG_X] >= 0) {
		mov_rr(code, opts->flag_regs[FLAG_X], opts->gen.scratch1, SZ_B);
	} else {
		int8_t offset = offsetof(m68k_context, flags);
		if (offset) {
			mov_rdispr(code, opts->gen.context_reg, offset, opts->gen.scratch1, SZ_B);
		} else {
			mov_rindr(code, opts->gen.context_reg, opts->gen.scratch1, SZ_B);
		}
	}
	for (int flag = FLAG_N; flag <= FLAG_C; flag++)
	{
		shl_ir(code, 1, opts->gen.scratch1, SZ_B);
		if (opts->flag_regs[flag] >= 0) {
			or_rr(code, opts->flag_regs[flag], opts->gen.scratch1, SZ_B);
		} else {
			or_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, flags) + flag, opts->gen.scratch1, SZ_B);
		}
	}
	retn(code);

	opts->set_sr = code->cur;
	for (int flag = FLAG_C; flag >= FLAG_X; flag--)
	{
		rcr_ir(code, 1, opts->gen.scratch1, SZ_B);
		if (opts->flag_regs[flag] >= 0) {
			setcc_r(code, CC_C, opts->flag_regs[flag]);
		} else {
			int8_t offset = offsetof(m68k_context, flags) + flag;
			if (offset) {
				setcc_rdisp(code, CC_C, opts->gen.context_reg, offset);
			} else {
				setcc_rind(code, CC_C, opts->gen.context_reg);
			}
		}
	}
	shr_ir(code, 8, opts->gen.scratch1, SZ_W);
	mov_rrdisp(code, opts->gen.scratch1, opts->gen.context_reg, offsetof(m68k_context, status), SZ_B);
	retn(code);

	opts->set_ccr = code->cur;
	for (int flag = FLAG_C; flag >= FLAG_X; flag--)
	{
		rcr_ir(code, 1, opts->gen.scratch1, SZ_B);
		if (opts->flag_regs[flag] >= 0) {
			setcc_r(code, CC_C, opts->flag_regs[flag]);
		} else {
			int8_t offset = offsetof(m68k_context, flags) + flag;
			if (offset) {
				setcc_rdisp(code, CC_C, opts->gen.context_reg, offset);
			} else {
				setcc_rind(code, CC_C, opts->gen.context_reg);
			}
		}
	}
	retn(code);

	opts->gen.handle_cycle_limit_int = code->cur;
	cmp_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, int_cycle), CYCLES, SZ_D);
	code_ptr do_int = code->cur + 1;
	jcc(code, CC_NC, code->cur + 2);
	cmp_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, sync_cycle), CYCLES, SZ_D);
	skip_sync = code->cur + 1;
	jcc(code, CC_C, code->cur + 2);
	call(code, opts->gen.save_context);
#ifdef X86_64
	mov_rr(code, opts->gen.context_reg, RDI, SZ_PTR);
	mov_rr(code, opts->gen.scratch1, RSI, SZ_D);
	test_ir(code, 8, RSP, SZ_D);
	adjust_rsp = code->cur + 1;
	jcc(code, CC_NZ, code->cur + 2);
	call(code, (code_ptr)sync_components);
	no_adjust = code->cur + 1;
	jmp(code, code->cur + 2);
	*adjust_rsp = code->cur - (adjust_rsp + 1);
	sub_ir(code, 8, RSP, SZ_PTR);
	call(code, (code_ptr)sync_components);
	add_ir(code, 8, RSP, SZ_PTR);
	*no_adjust = code->cur - (no_adjust+1);
#else
	push_r(code, opts->gen.scratch1);
	push_r(code, opts->gen.context_reg);
	call(code, (code_ptr)sync_components);
	add_ir(code, 8, RSP, SZ_D);
#endif
	mov_rr(code, RAX, opts->gen.context_reg, SZ_PTR);
	jmp(code, opts->gen.load_context);
	*skip_sync = code->cur - (skip_sync+1);
	retn(code);
	*do_int = code->cur - (do_int+1);
	//set target cycle to sync cycle
	mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, sync_cycle), LIMIT, SZ_D);
	//swap USP and SSP if not already in supervisor mode
	bt_irdisp(code, 5, opts->gen.context_reg, offsetof(m68k_context, status), SZ_B);
	code_ptr already_supervisor = code->cur + 1;
	jcc(code, CC_C, code->cur + 2);
	swap_ssp_usp(opts);
	*already_supervisor = code->cur - (already_supervisor+1);
	//save PC
	subi_areg(opts, 4, 7);
	areg_to_native(opts, 7, opts->gen.scratch2);
	call(code, opts->write_32_lowfirst);
	//save status register
	subi_areg(opts, 2, 7);
	call(code, opts->get_sr);
	areg_to_native(opts, 7, opts->gen.scratch2);
	call(code, opts->write_16);
	//update status register
	and_irdisp(code, 0xF8, opts->gen.context_reg, offsetof(m68k_context, status), SZ_B);
	mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, int_num), opts->gen.scratch1, SZ_B);
	or_ir(code, 0x20, opts->gen.scratch1, SZ_B);
	or_rrdisp(code, opts->gen.scratch1, opts->gen.context_reg, offsetof(m68k_context, status), SZ_B);
	//calculate interrupt vector address
	mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, int_num), opts->gen.scratch1, SZ_D);
	mov_rrdisp(code, opts->gen.scratch1, opts->gen.context_reg, offsetof(m68k_context, int_ack), SZ_W);
	shl_ir(code, 2, opts->gen.scratch1, SZ_D);
	add_ir(code, 0x60, opts->gen.scratch1, SZ_D);
	call(code, opts->read_32);
	call(code, opts->native_addr_and_sync);
	cycles(&opts->gen, 24);
	//discard function return address
	pop_r(code, opts->gen.scratch2);
	jmp_r(code, opts->gen.scratch1);

	opts->trap = code->cur;
	push_r(code, opts->gen.scratch2);
	//swap USP and SSP if not already in supervisor mode
	bt_irdisp(code, 5, opts->gen.context_reg, offsetof(m68k_context, status), SZ_B);
	already_supervisor = code->cur + 1;
	jcc(code, CC_C, code->cur + 2);
	swap_ssp_usp(opts);
	*already_supervisor = code->cur - (already_supervisor+1);
	//save PC
	subi_areg(opts, 4, 7);
	areg_to_native(opts, 7, opts->gen.scratch2);
	call(code, opts->write_32_lowfirst);
	//save status register
	subi_areg(opts, 2, 7);
	call(code, opts->get_sr);
	areg_to_native(opts, 7, opts->gen.scratch2);
	call(code, opts->write_16);
	//set supervisor bit
	or_irdisp(code, 0x20, opts->gen.context_reg, offsetof(m68k_context, status), SZ_B);
	//calculate vector address
	pop_r(code, opts->gen.scratch1);
	shl_ir(code, 2, opts->gen.scratch1, SZ_D);
	call(code, opts->read_32);
	call(code, opts->native_addr_and_sync);
	cycles(&opts->gen, 18);
	jmp_r(code, opts->gen.scratch1);
}