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

    drcuml.c

    Universal machine language for dynamic recompiling CPU cores.

    Copyright Aaron Giles
    Released for general non-commercial use under the MAME license
    Visit http://mamedev.org for licensing and usage restrictions.

****************************************************************************

    Future improvements/changes:

    * UML optimizer:
        - constant folding

    * Write a back-end validator:
        - checks all combinations of memory/register/immediate on all params
        - checks behavior of all opcodes

    * Extend registers to 16? Depends on if PPC can use them

    * Support for FPU exceptions

    * New instructions?
        - FCOPYI, ICOPYF
            copy raw between float and integer registers

        - VALID opcode_desc,handle,param
            checksum/compare code referenced by opcode_desc; if not
            matching, generate exception with handle,param

        - RECALL handle
            change code at caller to call handle in the future

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

#include "drcuml.h"
#include "drcumlsh.h"
#include "eminline.h"
#include "mame.h"
#include <stdarg.h>
#include <setjmp.h>


/***************************************************************************
    DEBUGGING
***************************************************************************/

#define VALIDATE_BACKEND		(0)
#define LOG_SIMPLIFICATIONS		(0)



/***************************************************************************
    CONSTANTS
***************************************************************************/

/* opcode validation condition/flag valid bitmasks */
#define OPFLAGS_NONE	0x00
#define OPFLAGS_C		DRCUML_FLAG_C
#define OPFLAGS_SZ		(DRCUML_FLAG_S | DRCUML_FLAG_Z)
#define OPFLAGS_SZC		(DRCUML_FLAG_S | DRCUML_FLAG_Z | DRCUML_FLAG_C)
#define OPFLAGS_SZV		(DRCUML_FLAG_S | DRCUML_FLAG_Z | DRCUML_FLAG_V)
#define OPFLAGS_SZVC	(DRCUML_FLAG_S | DRCUML_FLAG_Z | DRCUML_FLAG_V | DRCUML_FLAG_C)
#define OPFLAGS_UZC		(DRCUML_FLAG_U | DRCUML_FLAG_Z | DRCUML_FLAG_C)
#define OPFLAGS_ALL		0x1f
#define OPFLAGS_P1		0x81
#define OPFLAGS_P2		0x82
#define OPFLAGS_P3		0x83
#define OPFLAGS_P4		0x84

/* parameter input/output states */
#define PIO_IN			0x01
#define PIO_OUT			0x02
#define PIO_INOUT		(PIO_IN | PIO_OUT)

/* parameter sizes */
#define PSIZE_4			DRCUML_SIZE_DWORD
#define PSIZE_8			DRCUML_SIZE_QWORD
#define PSIZE_OP		0x80
#define PSIZE_P1		0x81
#define PSIZE_P2		0x82
#define PSIZE_P3		0x83
#define PSIZE_P4		0x84

/* basic parameter types */
#define PTYPES_NONE		0
#define PTYPES_IMM		(1 << DRCUML_PTYPE_IMMEDIATE)
#define PTYPES_IREG		(1 << DRCUML_PTYPE_INT_REGISTER)
#define PTYPES_FREG		(1 << DRCUML_PTYPE_FLOAT_REGISTER)
#define PTYPES_MVAR		(1 << DRCUML_PTYPE_MAPVAR)
#define PTYPES_MEM		(1 << DRCUML_PTYPE_MEMORY)

/* special parameter types */
#define PTYPES_PTR		(PTYPES_MEM | 0x1000)
#define PTYPES_STATE	(PTYPES_MEM | 0x2000)
#define PTYPES_STR		(PTYPES_MEM | 0x3000)
#define PTYPES_CFUNC	(PTYPES_MEM | 0x4000)
#define PTYPES_HAND		(PTYPES_MEM | 0x5000)
#define PTYPES_SIZE		(PTYPES_IMM | 0x8000)
#define PTYPES_SPACE	(PTYPES_IMM | 0x9000)
#define PTYPES_SPSZ		(PTYPES_IMM | 0xa000)
#define PTYPES_FMOD		(PTYPES_IMM | 0xb000)
#define PTYPES_LAB		(PTYPES_IMM | 0xc000)

/* combinations of types */
#define PTYPES_IRM		(PTYPES_IREG | PTYPES_MEM)
#define PTYPES_FRM		(PTYPES_FREG | PTYPES_MEM)
#define PTYPES_IMV		(PTYPES_IMM | PTYPES_MVAR)
#define PTYPES_IANY		(PTYPES_IRM | PTYPES_IMV)
#define PTYPES_FANY		(PTYPES_FRM)

/* test result undefined */
#define UNDEFINED		0x19bb7a1005fde439
#define UNDEFINED_U64	U64(0x19bb7a1005fde439)



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

/* structure describing a UML symbol */
typedef struct _drcuml_symbol drcuml_symbol;
struct _drcuml_symbol
{
	drcuml_symbol *			next;				/* link to the next symbol */
	drccodeptr				base;				/* base of the symbol */
	UINT32					length;				/* length of the symbol */
	char					symname[1];			/* name of the symbol */
};


/* structure describing UML generation state */
struct _drcuml_state
{
	const device_config *	device;				/* CPU device we are associated with */
	drccache *				cache;				/* pointer to the codegen cache */
	drcuml_block *			blocklist;			/* list of active blocks */
	const drcbe_interface *	beintf;				/* backend interface pointer */
	drcbe_state *			bestate;			/* pointer to the back-end state */
	drcuml_codehandle *		handlelist;			/* head of linked list of handles */
	FILE *					umllog;				/* handle to the UML logfile */
	drcuml_symbol *			symlist;			/* head of linked list of symbols */
	drcuml_symbol **		symtailptr;			/* pointer to tail of linked list of symbols */
};


/* structure describing UML codegen block */
struct _drcuml_block
{
	drcuml_state *			drcuml;				/* pointer back to the owning UML */
	drcuml_block *			next;				/* pointer to next block */
	drcuml_instruction *	inst;				/* pointer to the instruction list */
	UINT8					inuse;				/* this block is in use */
	UINT32					maxinst;			/* maximum number of instructions */
	UINT32					nextinst;			/* next instruction to fill in the cache */
	jmp_buf	*				errorbuf;			/* setjmp buffer for deep error handling */
};


/* structure describing a global handle */
struct _drcuml_codehandle
{
	drccodeptr				code;				/* pointer to the associated code */
	char *					string;				/* pointer to string attached to handle */
	drcuml_codehandle *		next;				/* link to next handle in the list */
	drcuml_state *			drcuml;				/* pointer to owning object */
};


/* structure describing back-end validation test */
typedef struct _bevalidate_test bevalidate_test;
struct _bevalidate_test
{
	drcuml_opcode			opcode;
	UINT8					size;
	UINT8					iflags;
	UINT8					flags;
	UINT64					param[4];
};



/***************************************************************************
    TABLES
***************************************************************************/

/* macro to simplify the table */
#define PINFO(inout, size, types)									{ PIO_##inout, PSIZE_##size, PTYPES_##types }
#define OPINFO0(op,str,sizes,cond,iflag,oflag,mflag)				{ DRCUML_OP_##op, str, sizes, cond, OPFLAGS_##iflag, OPFLAGS_##oflag, OPFLAGS_##mflag, { { 0 } } },
#define OPINFO1(op,str,sizes,cond,iflag,oflag,mflag,p0)				{ DRCUML_OP_##op, str, sizes, cond, OPFLAGS_##iflag, OPFLAGS_##oflag, OPFLAGS_##mflag, { p0 } },
#define OPINFO2(op,str,sizes,cond,iflag,oflag,mflag,p0,p1)			{ DRCUML_OP_##op, str, sizes, cond, OPFLAGS_##iflag, OPFLAGS_##oflag, OPFLAGS_##mflag, { p0, p1 } },
#define OPINFO3(op,str,sizes,cond,iflag,oflag,mflag,p0,p1,p2)		{ DRCUML_OP_##op, str, sizes, cond, OPFLAGS_##iflag, OPFLAGS_##oflag, OPFLAGS_##mflag, { p0, p1, p2 } },
#define OPINFO4(op,str,sizes,cond,iflag,oflag,mflag,p0,p1,p2,p3)	{ DRCUML_OP_##op, str, sizes, cond, OPFLAGS_##iflag, OPFLAGS_##oflag, OPFLAGS_##mflag, { p0, p1, p2, p3 } },

/* opcode validation table */
static const drcuml_opcode_info opcode_info_source[] =
{
	/* Compile-time opcodes */
	OPINFO1(HANDLE,  "handle",   4,   FALSE, NONE, NONE, NONE, PINFO(IN, OP, HAND))
	OPINFO2(HASH,    "hash",     4,   FALSE, NONE, NONE, NONE, PINFO(IN, OP, IMV), PINFO(IN, OP, IMV))
	OPINFO1(LABEL,   "label",    4,   FALSE, NONE, NONE, NONE, PINFO(IN, OP, LAB))
	OPINFO1(COMMENT, "comment",  4,   FALSE, NONE, NONE, NONE, PINFO(IN, OP, STR))
	OPINFO2(MAPVAR,  "mapvar",   4,   FALSE, NONE, NONE, NONE, PINFO(OUT, OP, MVAR), PINFO(IN, OP, IMV))

	/* Control Flow Operations */
	OPINFO0(NOP,     "nop",      4,   FALSE, NONE, NONE, NONE)
	OPINFO1(DEBUG,   "debug",    4,   FALSE, NONE, NONE, ALL,  PINFO(IN, OP, IANY))
	OPINFO1(EXIT,    "exit",     4,   TRUE,  NONE, NONE, ALL,  PINFO(IN, OP, IANY))
	OPINFO3(HASHJMP, "hashjmp",  4,   FALSE, NONE, NONE, ALL,  PINFO(IN, OP, IANY), PINFO(IN, OP, IANY), PINFO(IN, OP, HAND))
	OPINFO1(JMP,     "jmp",      4,   TRUE,  NONE, NONE, NONE, PINFO(IN, OP, LAB))
	OPINFO2(EXH,     "exh",      4,   TRUE,  NONE, NONE, ALL,  PINFO(IN, OP, HAND), PINFO(IN, OP, IANY))
	OPINFO1(CALLH,   "callh",    4,   TRUE,  NONE, NONE, ALL,  PINFO(IN, OP, HAND))
	OPINFO0(RET,     "ret",      4,   TRUE,  NONE, NONE, ALL)
	OPINFO2(CALLC,   "callc",    4,   TRUE,  NONE, NONE, ALL,  PINFO(IN, OP, CFUNC), PINFO(IN, OP, PTR))
	OPINFO2(RECOVER, "recover",  4,   FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, MVAR))

	/* Internal Register Operations */
	OPINFO1(SETFMOD, "setfmod",  4,   FALSE, NONE, NONE, ALL,  PINFO(IN, OP, IANY))
	OPINFO1(GETFMOD, "getfmod",  4,   FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, IRM))
	OPINFO1(GETEXP,  "getexp",   4,   FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, IRM))
	OPINFO2(GETFLGS, "getflgs",  4,   FALSE, P2,   NONE, ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IMV))
	OPINFO1(SAVE,    "save",     4,   FALSE, ALL,  NONE, ALL,  PINFO(OUT, OP, STATE))
	OPINFO1(RESTORE, "restore",  4,   FALSE, NONE, ALL,  ALL,  PINFO(IN, OP, STATE))

	/* Integer Operations */
	OPINFO4(LOAD,    "!load",    4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, PTR), PINFO(IN, 4, IANY), PINFO(IN, OP, SIZE))
	OPINFO4(LOADS,   "!loads",   4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, PTR), PINFO(IN, 4, IANY), PINFO(IN, OP, SIZE))
	OPINFO4(STORE,   "!store",   4|8, FALSE, NONE, NONE, ALL,  PINFO(IN, OP, PTR), PINFO(IN, 4, IANY), PINFO(IN, OP, IANY), PINFO(IN, OP, SIZE))
	OPINFO3(READ,    "!read",    4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, IRM), PINFO(IN, 4, IANY), PINFO(IN, OP, SPSZ))
	OPINFO4(READM,   "!readm",   4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, IRM), PINFO(IN, 4, IANY), PINFO(IN, OP, IANY), PINFO(IN, OP, SPSZ))
	OPINFO3(WRITE,   "!write",   4|8, FALSE, NONE, NONE, ALL,  PINFO(IN, 4, IANY), PINFO(IN, OP, IANY), PINFO(IN, OP, SPSZ))
	OPINFO4(WRITEM,  "!writem",  4|8, FALSE, NONE, NONE, ALL,  PINFO(IN, 4, IANY), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY), PINFO(IN, OP, SPSZ))
	OPINFO2(CARRY,   "!carry",   4|8, FALSE, NONE, C,    ALL,  PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO2(MOV,     "!mov",     4|8, TRUE,  NONE, NONE, NONE, PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY))
	OPINFO1(SET,     "!set",     4|8, TRUE,  NONE, NONE, ALL,  PINFO(OUT, OP, IRM))
	OPINFO3(SEXT,    "!sext",    4|8, FALSE, NONE, SZ,   ALL,  PINFO(OUT, OP, IRM), PINFO(IN, P3, IANY), PINFO(IN, OP, SIZE))
	OPINFO4(ROLAND,  "!roland",  4|8, FALSE, NONE, SZ,   ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO4(ROLINS,  "!rolins",  4|8, FALSE, NONE, SZ,   ALL,  PINFO(INOUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO3(ADD,     "!add",     4|8, FALSE, NONE, SZVC, ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO3(ADDC,    "!addc",    4|8, FALSE, C,    SZVC, ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO3(SUB,     "!sub",     4|8, FALSE, NONE, SZVC, ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO3(SUBB,    "!subb",    4|8, FALSE, C,    SZVC, ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO2(CMP,     "!cmp",     4|8, FALSE, NONE, SZVC, ALL,  PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO4(MULU,    "!mulu",    4|8, FALSE, NONE, SZV,  ALL,  PINFO(OUT, OP, IRM), PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO4(MULS,    "!muls",    4|8, FALSE, NONE, SZV,  ALL,  PINFO(OUT, OP, IRM), PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO4(DIVU,    "!divu",    4|8, FALSE, NONE, SZV,  ALL,  PINFO(OUT, OP, IRM), PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO4(DIVS,    "!divs",    4|8, FALSE, NONE, SZV,  ALL,  PINFO(OUT, OP, IRM), PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO3(AND,     "!and",     4|8, FALSE, NONE, SZ,   ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO2(TEST,    "!test",    4|8, FALSE, NONE, SZ,   ALL,  PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO3(OR,      "!or",      4|8, FALSE, NONE, SZ,   ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO3(XOR,     "!xor",     4|8, FALSE, NONE, SZ,   ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO2(LZCNT,   "!lzcnt",   4|8, FALSE, NONE, SZ,   ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY))
	OPINFO2(BSWAP,   "!bswap",   4|8, FALSE, NONE, SZ,   ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY))
	OPINFO3(SHL,     "!shl",     4|8, FALSE, NONE, SZC,  ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO3(SHR,     "!shr",     4|8, FALSE, NONE, SZC,  ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO3(SAR,     "!sar",     4|8, FALSE, NONE, SZC,  ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO3(ROL,     "!rol",     4|8, FALSE, NONE, SZC,  ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO3(ROLC,    "!rolc",    4|8, FALSE, C,    SZC,  ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO3(ROR,     "!ror",     4|8, FALSE, NONE, SZC,  ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))
	OPINFO3(RORC,    "!rorc",    4|8, FALSE, C,    SZC,  ALL,  PINFO(OUT, OP, IRM), PINFO(IN, OP, IANY), PINFO(IN, OP, IANY))

	/* Floating Point Operations */
	OPINFO3(FLOAD,   "f#load",   4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, FRM), PINFO(IN, OP, PTR), PINFO(IN, 4, IANY))
	OPINFO3(FSTORE,  "f#store",  4|8, FALSE, NONE, NONE, ALL,  PINFO(IN, OP, PTR), PINFO(IN, 4, IANY), PINFO(IN, OP, FRM))
	OPINFO3(FREAD,   "f#read",   4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, FRM), PINFO(IN, 4, IANY), PINFO(IN, OP, SPACE))
	OPINFO3(FWRITE,  "f#write",  4|8, FALSE, NONE, NONE, ALL,  PINFO(IN, 4, IANY), PINFO(IN, OP, FANY), PINFO(IN, OP, SPACE))
	OPINFO2(FMOV,    "f#mov",    4|8, TRUE,  NONE, NONE, NONE, PINFO(OUT, OP, FRM), PINFO(IN, OP, FANY))
	OPINFO4(FTOINT,  "f#toint",  4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, P3, IRM), PINFO(IN, OP, FANY), PINFO(IN, OP, SIZE), PINFO(IN, OP, FMOD))
	OPINFO3(FFRINT,  "f#frint",  4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, FRM), PINFO(IN, P3, IANY), PINFO(IN, OP, SIZE))
	OPINFO3(FFRFLT,  "f#frflt",  4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, FRM), PINFO(IN, P3, FANY), PINFO(IN, OP, SIZE))
	OPINFO2(FRNDS,   "f#rnds",     8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, FRM), PINFO(IN, P3, FANY))
	OPINFO3(FADD,    "f#add",    4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, FRM), PINFO(IN, OP, FANY), PINFO(IN, OP, FANY))
	OPINFO3(FSUB,    "f#sub",    4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, FRM), PINFO(IN, OP, FANY), PINFO(IN, OP, FANY))
	OPINFO2(FCMP,    "f#cmp",    4|8, FALSE, NONE, UZC,  ALL,  PINFO(IN, OP, FANY), PINFO(IN, OP, FANY))
	OPINFO3(FMUL,    "f#mul",    4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, FRM), PINFO(IN, OP, FANY), PINFO(IN, OP, FANY))
	OPINFO3(FDIV,    "f#div",    4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, FRM), PINFO(IN, OP, FANY), PINFO(IN, OP, FANY))
	OPINFO2(FNEG,    "f#neg",    4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, FRM), PINFO(IN, OP, FANY))
	OPINFO2(FABS,    "f#abs",    4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, FRM), PINFO(IN, OP, FANY))
	OPINFO2(FSQRT,   "f#sqrt",   4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, FRM), PINFO(IN, OP, FANY))
	OPINFO2(FRECIP,  "f#recip",  4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, FRM), PINFO(IN, OP, FANY))
	OPINFO2(FRSQRT,  "f#rsqrt",  4|8, FALSE, NONE, NONE, ALL,  PINFO(OUT, OP, FRM), PINFO(IN, OP, FANY))
};



/***************************************************************************
    GLOBAL VARIABLES
***************************************************************************/

/* we presume the existence of a C back-end interface */
extern const drcbe_interface drcbe_c_be_interface;

/* extern a reference to the native DRC interface if it exists */
#ifndef NATIVE_DRC
#define NATIVE_DRC drcbe_c_be_interface
#else
extern const drcbe_interface NATIVE_DRC;
#endif

/* table that is built up on first alloc which looks up via opcode */
static const drcuml_opcode_info *opcode_info_table[DRCUML_OP_MAX];



/***************************************************************************
    FUNCTION PROTOTYPES
***************************************************************************/

static void optimize_block(drcuml_block *block);
static void simplify_instruction_with_no_flags(drcuml_block *block, drcuml_instruction *inst);

static void disassemble_block(drcuml_block *block);
static const char *get_comment_text(const drcuml_instruction *inst);

static void validate_instruction(drcuml_block *block, const drcuml_instruction *inst);

static void validate_backend(drcuml_state *drcuml);
static void bevalidate_iterate_over_params(drcuml_state *drcuml, drcuml_codehandle **handles, const bevalidate_test *test, drcuml_parameter *paramlist, int pnum);
static void bevalidate_iterate_over_flags(drcuml_state *drcuml, drcuml_codehandle **handles, const bevalidate_test *test, drcuml_parameter *paramlist);
static void bevalidate_execute(drcuml_state *drcuml, drcuml_codehandle **handles, const bevalidate_test *test, const drcuml_parameter *paramlist, UINT8 flagmask);
static void bevalidate_initialize_random_state(drcuml_state *drcuml, drcuml_block *block, drcuml_machine_state *state);
static int bevalidate_populate_state(drcuml_block *block, drcuml_machine_state *state, const bevalidate_test *test, const drcuml_parameter *paramlist, drcuml_parameter *params, UINT64 *parammem);
static int bevalidate_verify_state(drcuml_state *drcuml, const drcuml_machine_state *istate, drcuml_machine_state *state, const bevalidate_test *test, UINT32 flags, const drcuml_parameter *params, const drcuml_instruction *testinst, drccodeptr codestart, drccodeptr codeend, UINT8 flagmask);



/***************************************************************************
    INLINE FUNCTIONS
***************************************************************************/

/*-------------------------------------------------
    rol32 - perform a 32-bit left rotate
-------------------------------------------------*/

INLINE UINT32 rol32(UINT32 source, UINT8 count)
{
	count &= 31;
	return (source << count) | (source >> (32 - count));
}


/*-------------------------------------------------
    rol64 - perform a 64-bit left rotate
-------------------------------------------------*/

INLINE UINT64 rol64(UINT64 source, UINT8 count)
{
	count &= 63;
	return (source << count) | (source >> (64 - count));
}


/*-------------------------------------------------
    effective_inflags - return the effective
    input flags based on any conditions encoded
    in an instruction
-------------------------------------------------*/

INLINE UINT8 effective_inflags(const drcuml_instruction *inst, const drcuml_opcode_info *opinfo)
{
	static const UINT8 flags_for_condition[] =
	{
		/* DRCUML_COND_Z */		DRCUML_FLAG_Z,
		/* DRCUML_COND_NZ */	DRCUML_FLAG_Z,
		/* DRCUML_COND_S */		DRCUML_FLAG_S,
		/* DRCUML_COND_NS */	DRCUML_FLAG_S,
		/* DRCUML_COND_C */		DRCUML_FLAG_C,
		/* DRCUML_COND_NC */	DRCUML_FLAG_C,
		/* DRCUML_COND_V */		DRCUML_FLAG_V,
		/* DRCUML_COND_NV */	DRCUML_FLAG_V,
		/* DRCUML_COND_U */		DRCUML_FLAG_U,
		/* DRCUML_COND_NU */	DRCUML_FLAG_U,
		/* DRCUML_COND_A */		DRCUML_FLAG_C | DRCUML_FLAG_Z,
		/* DRCUML_COND_BE */	DRCUML_FLAG_C | DRCUML_FLAG_Z,
		/* DRCUML_COND_G */		DRCUML_FLAG_S | DRCUML_FLAG_V | DRCUML_FLAG_Z,
		/* DRCUML_COND_LE */	DRCUML_FLAG_S | DRCUML_FLAG_V | DRCUML_FLAG_Z,
		/* DRCUML_COND_L */		DRCUML_FLAG_S | DRCUML_FLAG_V,
		/* DRCUML_COND_GE */	DRCUML_FLAG_S | DRCUML_FLAG_V
	};
	UINT8 flags = opinfo->inflags;
	if (flags & 0x80)
		flags = inst->param[flags - OPFLAGS_P1].value & OPFLAGS_ALL;
	if (inst->condition != DRCUML_COND_ALWAYS)
		flags |= flags_for_condition[inst->condition & 0x0f];
	return flags;
}


/*-------------------------------------------------
    effective_outflags - return the effective
    output flags based on any conditions encoded
    in an instruction
-------------------------------------------------*/

INLINE UINT8 effective_outflags(const drcuml_instruction *inst, const drcuml_opcode_info *opinfo)
{
	UINT8 flags = opinfo->outflags;
	if (flags & 0x80)
		flags = inst->param[flags - OPFLAGS_P1].value & OPFLAGS_ALL;
	return flags;
}


/*-------------------------------------------------
    effective_psize - return the effective
    parameter size based on the instruction and
    parameters passed
-------------------------------------------------*/

INLINE UINT8 effective_psize(const drcuml_instruction *inst, const drcuml_opcode_info *opinfo, int pnum)
{
	switch (opinfo->param[pnum].size)
	{
		case PSIZE_4:	return 4;
		case PSIZE_8:	return 8;
		case PSIZE_OP:	return inst->size;
		case PSIZE_P1:	assert(inst->param[0].type == DRCUML_PTYPE_IMMEDIATE); return 1 << (inst->param[0].value & 3);
		case PSIZE_P2:	assert(inst->param[1].type == DRCUML_PTYPE_IMMEDIATE); return 1 << (inst->param[1].value & 3);
		case PSIZE_P3:	assert(inst->param[2].type == DRCUML_PTYPE_IMMEDIATE); return 1 << (inst->param[2].value & 3);
		case PSIZE_P4:	assert(inst->param[3].type == DRCUML_PTYPE_IMMEDIATE); return 1 << (inst->param[3].value & 3);
	}
	return inst->size;
}


/*-------------------------------------------------
    effective_test_psize - return the effective
    parameter size based on the size and fixed
    array of parameter values
-------------------------------------------------*/

INLINE UINT8 effective_test_psize(const drcuml_opcode_info *opinfo, int pnum, int instsize, const UINT64 *params)
{
	switch (opinfo->param[pnum].size)
	{
		case PSIZE_4:	return 4;
		case PSIZE_8:	return 8;
		case PSIZE_OP:	return instsize;
		case PSIZE_P1:	return 1 << (params[0] & 3);
		case PSIZE_P2:	return 1 << (params[1] & 3);
		case PSIZE_P3:	return 1 << (params[2] & 3);
		case PSIZE_P4:	return 1 << (params[3] & 3);
	}
	return instsize;
}


/*-------------------------------------------------
    param_is_immediate - return TRUE if the
    given instruction parameter is an immediate
-------------------------------------------------*/

INLINE int param_is_immediate(const drcuml_instruction *inst, int pnum)
{
	return (inst->param[pnum].type == DRCUML_PTYPE_IMMEDIATE);
}


/*-------------------------------------------------
    param_is_immediate_value - return TRUE if the
    given instruction parameter is an immediate
    with the provided value
-------------------------------------------------*/

INLINE int param_is_immediate_value(const drcuml_instruction *inst, int pnum, UINT64 value)
{
	return (inst->param[pnum].type == DRCUML_PTYPE_IMMEDIATE && inst->param[pnum].value == value);
}


/*-------------------------------------------------
    param_equal - return TRUE if the two provided
    parameters are equal
-------------------------------------------------*/

INLINE int param_equal(const drcuml_instruction *inst, int pnum1, int pnum2)
{
	return (inst->param[pnum1].type == inst->param[pnum2].type && inst->param[pnum1].value == inst->param[pnum2].value);
}


/*-------------------------------------------------
    convert_to_nop - convert an instruction
    inline to a NOP
-------------------------------------------------*/

INLINE void convert_to_nop(drcuml_instruction *inst)
{
	inst->opcode = DRCUML_OP_NOP;
	inst->size = 4;
	inst->condition = DRCUML_COND_ALWAYS;
	inst->numparams = 0;
}


/*-------------------------------------------------
    convert_to_mov_immediate - convert an
    instruction inline to a MOV immediate
-------------------------------------------------*/

INLINE void convert_to_mov_immediate(drcuml_instruction *inst, UINT64 immediate)
{
	inst->opcode = DRCUML_OP_MOV;
	inst->condition = DRCUML_COND_ALWAYS;
	inst->numparams = 2;
	inst->param[1].type = DRCUML_PTYPE_IMMEDIATE;
	inst->param[1].value = immediate;
}


/*-------------------------------------------------
    convert_to_mov_param - convert an
    instruction inline to a MOV with a source
    parameter given by the existing parameter
    index pnum
-------------------------------------------------*/

INLINE void convert_to_mov_param(drcuml_instruction *inst, int pnum)
{
	inst->opcode = DRCUML_OP_MOV;
	inst->condition = DRCUML_COND_ALWAYS;
	inst->numparams = 2;
	if (pnum != 1)
		inst->param[1] = inst->param[pnum];
}



/***************************************************************************
    INITIALIZATION/TEARDOWN
***************************************************************************/

/*-------------------------------------------------
    drcuml_alloc - allocate state for the code
    generator and initialize the back-end
-------------------------------------------------*/

drcuml_state *drcuml_alloc(const device_config *device, drccache *cache, UINT32 flags, int modes, int addrbits, int ignorebits)
{
	drcuml_state *drcuml;
	int opnum;

	/* allocate state */
	drcuml = (drcuml_state *)drccache_memory_alloc(cache, sizeof(*drcuml));
	if (drcuml == NULL)
		return NULL;
	memset(drcuml, 0, sizeof(*drcuml));

	/* initialize the state */
	drcuml->device = device;
	drcuml->cache = cache;
	drcuml->beintf = (flags & DRCUML_OPTION_USE_C) ? &drcbe_c_be_interface : &NATIVE_DRC;
	drcuml->symtailptr = &drcuml->symlist;

	/* if we're to log, create the logfile */
	if (flags & DRCUML_OPTION_LOG_UML)
		drcuml->umllog = fopen("drcuml.asm", "w");

	/* allocate the back-end */
	drcuml->bestate = (*drcuml->beintf->be_alloc)(drcuml, cache, device, flags, modes, addrbits, ignorebits);
	if (drcuml->bestate == NULL)
	{
		drcuml_free(drcuml);
		return NULL;
	}

	/* update the valid opcode table */
	for (opnum = 0; opnum < ARRAY_LENGTH(opcode_info_source); opnum++)
		opcode_info_table[opcode_info_source[opnum].opcode] = &opcode_info_source[opnum];

	return drcuml;
}


/*-------------------------------------------------
    drcuml_get_backend_info - return information
    about the back-end
-------------------------------------------------*/

void drcuml_get_backend_info(drcuml_state *drcuml, drcbe_info *info)
{
	(*drcuml->beintf->be_get_info)(drcuml->bestate, info);
}


/*-------------------------------------------------
    drcuml_reset - reset the state completely,
    flushing the cache and all information
-------------------------------------------------*/

void drcuml_reset(drcuml_state *drcuml)
{
	drcuml_codehandle *handle;
	jmp_buf errorbuf;

	/* flush the cache */
	drccache_flush(drcuml->cache);

	/* if we error here, we are screwed */
	if (setjmp(errorbuf) != 0)
		fatalerror("Out of cache space in drcuml_reset");

	/* reset all handle code pointers */
	for (handle = drcuml->handlelist; handle != NULL; handle = handle->next)
		handle->code = NULL;

	/* call the backend to reset */
	(*drcuml->beintf->be_reset)(drcuml->bestate);

	/* do a one-time validation if requested */
	if (VALIDATE_BACKEND)
	{
		static int validated = FALSE;
		if (!validated)
		{
			validated = TRUE;
			validate_backend(drcuml);
		}
	}
}


/*-------------------------------------------------
    drcuml_free - free state for the code generator
    and the back-end
-------------------------------------------------*/

void drcuml_free(drcuml_state *drcuml)
{
	/* free the back-end */
	if (drcuml->bestate != NULL)
		(*drcuml->beintf->be_free)(drcuml->bestate);

	/* free all the blocks */
	while (drcuml->blocklist != NULL)
	{
		drcuml_block *block = drcuml->blocklist;

		/* remove the item from the list */
		drcuml->blocklist = block->next;

		/* free memory */
		if (block->inst != NULL)
			free(block->inst);
		free(block);
	}

	/* free all the symbols */
	while (drcuml->symlist != NULL)
	{
		drcuml_symbol *sym = drcuml->symlist;
		drcuml->symlist = sym->next;
		free(sym);
	}

	/* close any files */
	if (drcuml->umllog != NULL)
		fclose(drcuml->umllog);
}



/***************************************************************************
    CODE BLOCK GENERATION
***************************************************************************/

/*-------------------------------------------------
    drcuml_block_begin - begin a new code block
-------------------------------------------------*/

drcuml_block *drcuml_block_begin(drcuml_state *drcuml, UINT32 maxinst, jmp_buf *errorbuf)
{
	drcuml_block *bestblock = NULL;
	drcuml_block *block;

	/* find an inactive block that matches our qualifications */
	for (block = drcuml->blocklist; block != NULL; block = block->next)
		if (!block->inuse && block->maxinst >= maxinst && (bestblock == NULL || block->maxinst < bestblock->maxinst))
			bestblock = block;

	/* if we failed to find one, allocate a new one */
	if (bestblock == NULL)
	{
		/* allocate the block structure itself */
		bestblock = (drcuml_block *)malloc(sizeof(*bestblock));
		if (bestblock == NULL)
			fatalerror("Out of memory allocating block in drcuml_block_begin");
		memset(bestblock, 0, sizeof(*bestblock));

		/* fill in the structure */
		bestblock->drcuml = drcuml;
		bestblock->next = drcuml->blocklist;
		bestblock->maxinst = maxinst * 3 / 2;
		bestblock->inst = (drcuml_instruction *)malloc(sizeof(drcuml_instruction) * bestblock->maxinst);
		if (bestblock->inst == NULL)
			fatalerror("Out of memory allocating instruction array in drcuml_block_begin");

		/* hook us into the list */
		drcuml->blocklist = bestblock;
	}

	/* set up the block information and return it */
	bestblock->inuse = TRUE;
	bestblock->nextinst = 0;
	bestblock->errorbuf = errorbuf;

	return bestblock;
}


/*-------------------------------------------------
    drcuml_block_append_0 - append an opcode with
    0 parameters to the block
-------------------------------------------------*/

void drcuml_block_append_0(drcuml_block *block, drcuml_opcode op, UINT8 size, UINT8 condition)
{
	drcuml_instruction *inst = &block->inst[block->nextinst++];

	assert(block->inuse);

	/* get a pointer to the next instruction */
	if (block->nextinst > block->maxinst)
		fatalerror("Overran maxinst in drcuml_block_append");

	/* fill in the instruction */
	inst->opcode = (drcuml_opcode)(UINT8)op;
	inst->size = size;
	inst->condition = condition;
	inst->flags = 0;
	inst->numparams = 0;

	/* validation */
	validate_instruction(block, inst);
}


/*-------------------------------------------------
    drcuml_block_append_1 - append an opcode with
    1 parameter to the block
-------------------------------------------------*/

void drcuml_block_append_1(drcuml_block *block, drcuml_opcode op, UINT8 size, UINT8 condition, drcuml_ptype p0type, drcuml_pvalue p0value)
{
	drcuml_instruction *inst = &block->inst[block->nextinst++];

	assert(block->inuse);

	/* get a pointer to the next instruction */
	if (block->nextinst > block->maxinst)
		fatalerror("Overran maxinst in drcuml_block_append");

	/* fill in the instruction */
	inst->opcode = (drcuml_opcode)(UINT8)op;
	inst->size = size;
	inst->condition = condition;
	inst->flags = 0;
	inst->numparams = 1;
	inst->param[0].type = p0type;
	inst->param[0].value = p0value;

	/* validation */
	validate_instruction(block, inst);
}


/*-------------------------------------------------
    drcuml_block_append_2 - append an opcode with
    2 parameters to the block
-------------------------------------------------*/

void drcuml_block_append_2(drcuml_block *block, drcuml_opcode op, UINT8 size, UINT8 condition, drcuml_ptype p0type, drcuml_pvalue p0value, drcuml_ptype p1type, drcuml_pvalue p1value)
{
	drcuml_instruction *inst = &block->inst[block->nextinst++];

	assert(block->inuse);

	/* get a pointer to the next instruction */
	if (block->nextinst > block->maxinst)
		fatalerror("Overran maxinst in drcuml_block_append");

	/* fill in the instruction */
	inst->opcode = (drcuml_opcode)(UINT8)op;
	inst->size = size;
	inst->condition = condition;
	inst->flags = 0;
	inst->numparams = 2;
	inst->param[0].type = p0type;
	inst->param[0].value = p0value;
	inst->param[1].type = p1type;
	inst->param[1].value = p1value;

	/* validation */
	validate_instruction(block, inst);
}


/*-------------------------------------------------
    drcuml_block_append_3 - append an opcode with
    3 parameters to the block
-------------------------------------------------*/

void drcuml_block_append_3(drcuml_block *block, drcuml_opcode op, UINT8 size, UINT8 condition, drcuml_ptype p0type, drcuml_pvalue p0value, drcuml_ptype p1type, drcuml_pvalue p1value, drcuml_ptype p2type, drcuml_pvalue p2value)
{
	drcuml_instruction *inst = &block->inst[block->nextinst++];

	assert(block->inuse);

	/* get a pointer to the next instruction */
	if (block->nextinst > block->maxinst)
		fatalerror("Overran maxinst in drcuml_block_append");

	/* fill in the instruction */
	inst->opcode = (drcuml_opcode)(UINT8)op;
	inst->size = size;
	inst->condition = condition;
	inst->flags = 0;
	inst->numparams = 3;
	inst->param[0].type = p0type;
	inst->param[0].value = p0value;
	inst->param[1].type = p1type;
	inst->param[1].value = p1value;
	inst->param[2].type = p2type;
	inst->param[2].value = p2value;

	/* validation */
	validate_instruction(block, inst);
}


/*-------------------------------------------------
    drcuml_block_append_4 - append an opcode with
    4 parameters to the block
-------------------------------------------------*/

void drcuml_block_append_4(drcuml_block *block, drcuml_opcode op, UINT8 size, UINT8 condition, drcuml_ptype p0type, drcuml_pvalue p0value, drcuml_ptype p1type, drcuml_pvalue p1value, drcuml_ptype p2type, drcuml_pvalue p2value, drcuml_ptype p3type, drcuml_pvalue p3value)
{
	drcuml_instruction *inst = &block->inst[block->nextinst++];

	assert(block->inuse);

	/* get a pointer to the next instruction */
	if (block->nextinst > block->maxinst)
		fatalerror("Overran maxinst in drcuml_block_append");

	/* fill in the instruction */
	inst->opcode = (drcuml_opcode)(UINT8)op;
	inst->size = size;
	inst->condition = condition;
	inst->flags = 0;
	inst->numparams = 4;
	inst->param[0].type = p0type;
	inst->param[0].value = p0value;
	inst->param[1].type = p1type;
	inst->param[1].value = p1value;
	inst->param[2].type = p2type;
	inst->param[2].value = p2value;
	inst->param[3].type = p3type;
	inst->param[3].value = p3value;

	/* validation */
	validate_instruction(block, inst);
}


/*-------------------------------------------------
    drcuml_block_end - complete a code block and
    commit it to the cache via the back-end
-------------------------------------------------*/

void drcuml_block_end(drcuml_block *block)
{
	drcuml_state *drcuml = block->drcuml;

	assert(block->inuse);

	/* optimize the resulting code first */
	optimize_block(block);

	/* if we have a logfile, generate a disassembly of the block */
	if (drcuml->umllog != NULL)
		disassemble_block(block);

	/* generate the code via the back-end */
	(*drcuml->beintf->be_generate)(drcuml->bestate, block, block->inst, block->nextinst);

	/* block is no longer in use */
	block->inuse = FALSE;
}


/*-------------------------------------------------
    drcuml_block_abort - abort a code block in
    progress
-------------------------------------------------*/

void drcuml_block_abort(drcuml_block *block)
{
	assert(block->inuse);

	/* block is no longer in use */
	block->inuse = FALSE;

	/* unwind */
	longjmp(*block->errorbuf, 1);
}


/*-------------------------------------------------
    drcuml_hash_exists - return true if a hash
    entry exists for the given mode/pc
-------------------------------------------------*/

int drcuml_hash_exists(drcuml_state *drcuml, UINT32 mode, UINT32 pc)
{
	return (*drcuml->beintf->be_hash_exists)(drcuml->bestate, mode, pc);
}



/***************************************************************************
    CODE EXECUTION
***************************************************************************/

/*-------------------------------------------------
    drcuml_execute - execute at the given PC/mode
    for the specified cycles
-------------------------------------------------*/

int drcuml_execute(drcuml_state *drcuml, drcuml_codehandle *entry)
{
	return (*drcuml->beintf->be_execute)(drcuml->bestate, entry);
}



/***************************************************************************
    CODE HANDLES
***************************************************************************/

/*-------------------------------------------------
    drcuml_handle_alloc - allocate a new handle
-------------------------------------------------*/

drcuml_codehandle *drcuml_handle_alloc(drcuml_state *drcuml, const char *name)
{
	drcuml_codehandle *handle;
	char *string;

	/* allocate space for a copy of the string */
	string = (char *)drccache_memory_alloc(drcuml->cache, strlen(name) + 1);
	if (string == NULL)
		return NULL;
	strcpy(string, name);

	/* allocate a new handle info */
	handle = (drcuml_codehandle *)drccache_memory_alloc_near(drcuml->cache, sizeof(*handle));
	if (handle == NULL)
	{
		drccache_memory_free(drcuml->cache, string, strlen(name) + 1);
		return NULL;
	}
	memset(handle, 0, sizeof(*handle));

	/* fill in the rest of the info and add to the list of handles */
	handle->drcuml = drcuml;
	handle->string = string;
	handle->next = drcuml->handlelist;
	drcuml->handlelist = handle;

	return handle;
}


/*-------------------------------------------------
    drcuml_handle_set_codeptr - set a codeptr
    associated with a handle
-------------------------------------------------*/

void drcuml_handle_set_codeptr(drcuml_codehandle *handle, drccodeptr code)
{
	assert(handle->code == NULL);
	assert_in_cache(handle->drcuml->cache, code);
	handle->code = code;
}


/*-------------------------------------------------
    drcuml_handle_codeptr - get the pointer from
    a handle
-------------------------------------------------*/

drccodeptr drcuml_handle_codeptr(const drcuml_codehandle *handle)
{
	return handle->code;
}


/*-------------------------------------------------
    drcuml_handle_codeptr_addr - get the address
    of the pointer from a handle
-------------------------------------------------*/

drccodeptr *drcuml_handle_codeptr_addr(drcuml_codehandle *handle)
{
	return &handle->code;
}


/*-------------------------------------------------
    drcuml_handle_name - get the name of a handle
-------------------------------------------------*/

const char *drcuml_handle_name(const drcuml_codehandle *handle)
{
	return handle->string;
}



/***************************************************************************
    CODE LOGGING
***************************************************************************/

/*-------------------------------------------------
    drcuml_symbol_add - add a symbol to the
    internal symbol table
-------------------------------------------------*/

void drcuml_symbol_add(drcuml_state *drcuml, void *base, UINT32 length, const char *name)
{
	drcuml_symbol *symbol;

	/* allocate memory to hold the symbol */
	symbol = (drcuml_symbol *)malloc(sizeof(*symbol) + strlen(name));
	if (symbol == NULL)
		fatalerror("Out of memory allocating symbol in drcuml_symbol_add");

	/* fill in the structure */
	symbol->next = NULL;
	symbol->base = (drccodeptr)base;
	symbol->length = length;
	strcpy(symbol->symname, name);

	/* add to the tail of the list */
	*drcuml->symtailptr = symbol;
	drcuml->symtailptr = &symbol->next;
}


/*-------------------------------------------------
    drcuml_symbol_find - look up a symbol from the
    internal symbol table or return NULL if not
    found
-------------------------------------------------*/

const char *drcuml_symbol_find(drcuml_state *drcuml, void *base, UINT32 *offset)
{
	drccodeptr search = (drccodeptr)base;
	drcuml_symbol *symbol;

	/* simple linear search */
	for (symbol = drcuml->symlist; symbol != NULL; symbol = symbol->next)
		if (search >= symbol->base && search < symbol->base + symbol->length)
		{
			/* if no offset pointer, only match perfectly */
			if (offset == NULL && search != symbol->base)
				continue;

			/* return the offset and name */
			if (offset != NULL)
				*offset = search - symbol->base;
			return symbol->symname;
		}

	/* not found; return NULL */
	return NULL;
}


/*-------------------------------------------------
    drcuml_log_printf - directly printf to the UML
    log if generated
-------------------------------------------------*/

void drcuml_log_printf(drcuml_state *drcuml, const char *format, ...)
{
	/* if we have a file, print to it */
	if (drcuml->umllog != NULL)
	{
		va_list va;

		/* do the printf */
		va_start(va, format);
		vfprintf(drcuml->umllog, format, va);
		va_end(va);
	}
}


/*-------------------------------------------------
    drcuml_add_comment - attach a comment to the
    current output location in the specified block
-------------------------------------------------*/

void drcuml_add_comment(drcuml_block *block, const char *format, ...)
{
	char buffer[512];
	char *comment;
	va_list va;

	assert(block->inuse);

	/* do the printf */
	va_start(va, format);
	vsprintf(buffer, format, va);
	va_end(va);

	/* allocate space in the cache to hold the comment */
	comment = (char *)drccache_memory_alloc_temporary(block->drcuml->cache, strlen(buffer) + 1);
	if (comment == NULL)
		return;
	strcpy(comment, buffer);

	/* add an instruction with a pointer */
	drcuml_block_append_1(block, DRCUML_OP_COMMENT, 4, DRCUML_COND_ALWAYS, MEM(comment));
}


/*-------------------------------------------------
    drcuml_disasm - disassemble an
    instruction to the given buffer
-------------------------------------------------*/

void drcuml_disasm(const drcuml_instruction *inst, char *buffer, drcuml_state *drcuml)
{
	static const char *const conditions[] = { "z", "nz", "s", "ns", "c", "nc", "v", "nv", "u", "nu", "a", "be", "g", "le", "l", "ge" };
	static const char *const pound_size[] = { "?", "?", "?", "?", "s", "?", "?", "?", "d" };
	static const char *const bang_size[] = { "?", "b", "h", "?", "", "?", "?", "?", "d" };
	static const char *const fmods[] = { "trunc", "round", "ceil", "floor", "default" };
	static const char *const sizes[] = { "byte", "word", "dword", "qword" };
	const drcuml_opcode_info *opinfo = opcode_info_table[inst->opcode];
	char *dest = buffer;
	const char *opsrc;
	int pnum;

	assert(inst->opcode != DRCUML_OP_INVALID && inst->opcode < DRCUML_OP_MAX);

	/* start with the raw mnemonic and substitute sizes */
	for (opsrc = opinfo->mnemonic; *opsrc != 0; opsrc++)
		if (*opsrc == '!')
			dest += sprintf(dest, "%s", bang_size[inst->size]);
		else if (*opsrc == '#')
			dest += sprintf(dest, "%s", pound_size[inst->size]);
		else
			*dest++ = *opsrc;

	/* pad to 8 spaces */
	while (dest < &buffer[8])
		*dest++ = ' ';

	/* iterate through parameters */
	for (pnum = 0; pnum < inst->numparams; pnum++)
	{
		const drcuml_parameter *param = &inst->param[pnum];
		UINT16 typemask = opinfo->param[pnum].typemask;
		const char *symbol;
		UINT32 symoffset;

		/* start with a comma for all except the first parameter */
		if (pnum != 0)
			*dest++ = ',';

		/* ouput based on type */
		switch (param->type)
		{
			/* immediates have several special cases */
			case DRCUML_PTYPE_IMMEDIATE:

				/* size immediate */
				if (typemask == PTYPES_SIZE)
					dest += sprintf(dest, "%s", sizes[param->value]);

				/* address space immediate */
				else if (typemask == PTYPES_SPACE)
					dest += sprintf(dest, "%s", address_space_names[param->value]);

				/* size + address space immediate */
				else if (typemask == PTYPES_SPSZ)
					dest += sprintf(dest, "%s_%s", address_space_names[param->value / 16], sizes[param->value % 16]);

				/* fmod immediate */
				else if (typemask == PTYPES_FMOD)
					dest += sprintf(dest, "%s", fmods[param->value]);

				/* general immediate */
				else
				{
					int size = effective_psize(inst, opinfo, pnum);
					UINT64 value = param->value;

					/* truncate to size */
					if (size == 1) value = (UINT8)value;
					if (size == 2) value = (UINT16)value;
					if (size == 4) value = (UINT32)value;
					if ((UINT32)value == value)
						dest += sprintf(dest, "$%X", (UINT32)value);
					else
						dest += sprintf(dest, "$%X%08X", (UINT32)(value >> 32), (UINT32)value);
				}
				break;

			/* integer registers */
			case DRCUML_PTYPE_INT_REGISTER:
				if (param->value >= DRCUML_REG_I0 && param->value < DRCUML_REG_I_END)
					dest += sprintf(dest, "i%d", (UINT32)(param->value - DRCUML_REG_I0));
				else
					dest += sprintf(dest, "i(%X?)", (UINT32)param->value);
				break;

			/* floating point registers */
			case DRCUML_PTYPE_FLOAT_REGISTER:
				if (param->value >= DRCUML_REG_F0 && param->value < DRCUML_REG_F_END)
					dest += sprintf(dest, "f%d", (UINT32)(param->value - DRCUML_REG_F0));
				else
					dest += sprintf(dest, "f(%X?)", (UINT32)param->value);
				break;

			/* map variables */
			case DRCUML_PTYPE_MAPVAR:
				if (param->value >= DRCUML_MAPVAR_M0 && param->value < DRCUML_MAPVAR_END)
					dest += sprintf(dest, "m%d", (UINT32)(param->value - DRCUML_MAPVAR_M0));
				else
					dest += sprintf(dest, "m(%X?)", (UINT32)param->value);
				break;

			/* memory */
			case DRCUML_PTYPE_MEMORY:

				/* handle pointer */
				if (typemask == PTYPES_HAND)
					dest += sprintf(dest, "%s", drcuml_handle_name((const drcuml_codehandle *)(FPTR)param->value));

				/* string pointer */
				else if (typemask == PTYPES_STR)
					dest += sprintf(dest, "%s", (const char *)(FPTR)param->value);

				/* symbol */
				else if (drcuml != NULL && (symbol = drcuml_symbol_find(drcuml, (drccodeptr)(FPTR)param->value, &symoffset)) != NULL)
				{
					if (symoffset == 0)
						dest += sprintf(dest, "[%s]", symbol);
					else
						dest += sprintf(dest, "[%s+$%X]", symbol, symoffset);
				}

				/* cache memory */
				else if (drcuml != NULL && drccache_contains_pointer(drcuml->cache, (void *)(FPTR)param->value))
					dest += sprintf(dest, "[+$%X]", (UINT32)(FPTR)((drccodeptr)(FPTR)param->value - drccache_near(drcuml->cache)));

				/* general memory */
				else
					dest += sprintf(dest, "[[$%p]]", (void *)(FPTR)param->value);
				break;

			default:
				dest += sprintf(dest, "???");
				break;
		}
	}

	/* if there's a condition, append it */
	if (inst->condition != DRCUML_COND_ALWAYS)
		dest += sprintf(dest, ",%s", conditions[inst->condition & 0x0f]);

	/* if there are flags, append them */
	if (inst->flags != 0)
	{
		*dest++ = ',';
		if (inst->flags & DRCUML_FLAG_U)
			*dest++ = 'U';
		if (inst->flags & DRCUML_FLAG_S)
			*dest++ = 'S';
		if (inst->flags & DRCUML_FLAG_Z)
			*dest++ = 'Z';
		if (inst->flags & DRCUML_FLAG_V)
			*dest++ = 'V';
		if (inst->flags & DRCUML_FLAG_C)
			*dest++ = 'C';
	}

	/* ensure we are NULL-terminated */
	*dest = 0;
}



/***************************************************************************
    CODE BLOCK OPTIMIZATION
***************************************************************************/

/*-------------------------------------------------
    optimize_block - apply various optimizations
    to a block of code
-------------------------------------------------*/

static void optimize_block(drcuml_block *block)
{
	UINT32 mapvar[DRCUML_MAPVAR_END - DRCUML_MAPVAR_M0] = { 0 };
	int instnum;

	/* iterate over instructions */
	for (instnum = 0; instnum < block->nextinst; instnum++)
	{
		drcuml_instruction *inst = &block->inst[instnum];
		const drcuml_opcode_info *opinfo = opcode_info_table[inst->opcode];
		UINT8 remainingflags;
		int scannum, pnum;

		/* first compute what flags we need */
		inst->flags = 0;
		remainingflags = effective_outflags(inst, opinfo);

		/* scan ahead until we run out of possible remaining flags */
		for (scannum = instnum + 1; remainingflags != 0 && scannum < block->nextinst; scannum++)
		{
			const drcuml_instruction *scan = &block->inst[scannum];
			const drcuml_opcode_info *scaninfo = opcode_info_table[scan->opcode];

			/* any input flags are required */
			inst->flags |= effective_inflags(scan, scaninfo);

			/* if the scanahead instruction is unconditional, assume his flags are modified */
			if (scan->condition == DRCUML_COND_ALWAYS)
				remainingflags &= ~scaninfo->modflags;
		}

		/* track mapvars */
		if (inst->opcode == DRCUML_OP_MAPVAR)
			mapvar[inst->param[0].value - DRCUML_MAPVAR_M0] = inst->param[1].value;

		/* convert all mapvar parameters to immediates */
		else if (inst->opcode != DRCUML_OP_RECOVER)
			for (pnum = 0; pnum < inst->numparams; pnum++)
				if (inst->param[pnum].type == DRCUML_PTYPE_MAPVAR)
				{
					inst->param[pnum].type = DRCUML_PTYPE_IMMEDIATE;
					inst->param[pnum].value = mapvar[inst->param[pnum].value - DRCUML_MAPVAR_M0];
				}

		/* if we don't need any flags, then we can eliminate a lot of dumb operations */
		if (inst->flags == 0)
			simplify_instruction_with_no_flags(block, inst);
	}
}


/*-------------------------------------------------
    simplify_instruction_with_no_flags - simplify
    instructions that have immediate values we
    can evaluate at compile time
-------------------------------------------------*/

static void simplify_instruction_with_no_flags(drcuml_block *block, drcuml_instruction *inst)
{
	static const UINT64 instsizemask[] = { 0, 0, 0, 0, 0xffffffff, 0, 0, 0, U64(0xffffffffffffffff) };
	static const UINT64 paramsizemask[] = { 0xff, 0xffff, 0xffffffff, U64(0xffffffffffffffff) };
	drcuml_opcode origop = inst->opcode;
	drcuml_instruction orig = *inst;

	switch (inst->opcode)
	{
		/* READM: convert to READ if the mask is wide open */
		case DRCUML_OP_READM:
			if (param_is_immediate_value(inst, 2, paramsizemask[inst->param[3].value] & 3))
			{
				inst->opcode = DRCUML_OP_READ;
				inst->numparams = 3;
				inst->param[2] = inst->param[3];
			}
			break;

		/* WRITEM: convert to WRITE if the mask is wide open */
		case DRCUML_OP_WRITEM:
			if (param_is_immediate_value(inst, 2, paramsizemask[inst->param[3].value] & 3))
			{
				inst->opcode = DRCUML_OP_WRITE;
				inst->numparams = 3;
				inst->param[2] = inst->param[3];
			}
			break;

		/* SET: convert to MOV if constant condition */
		case DRCUML_OP_SET:
			if (inst->condition == DRCUML_COND_ALWAYS)
				convert_to_mov_immediate(inst, 1);
			break;

		/* MOV: convert to NOP if move-to-self */
		case DRCUML_OP_MOV:
			if (param_equal(inst, 0, 1))
				convert_to_nop(inst);
			break;

		/* SEXT: convert immediates to MOV */
		case DRCUML_OP_SEXT:
			if (param_is_immediate(inst, 1))
				switch (inst->param[2].value)
				{
					case DRCUML_SIZE_BYTE:	convert_to_mov_immediate(inst, (INT8)inst->param[1].value);		break;
					case DRCUML_SIZE_WORD:	convert_to_mov_immediate(inst, (INT16)inst->param[1].value);	break;
					case DRCUML_SIZE_DWORD:	convert_to_mov_immediate(inst, (INT32)inst->param[1].value);	break;
					case DRCUML_SIZE_QWORD:	convert_to_mov_immediate(inst, (INT64)inst->param[1].value);	break;
				}
			break;

		/* ROLAND: convert to MOV if all immediate, or to ROL or AND if one is not needed, or to SHL/SHR if the mask is right */
		case DRCUML_OP_ROLAND:
			if (param_is_immediate(inst, 1) && param_is_immediate(inst, 2) && param_is_immediate(inst, 3))
			{
				if (inst->size == 4)
					convert_to_mov_immediate(inst, rol32(inst->param[1].value, inst->param[2].value) & inst->param[3].value);
				else
					convert_to_mov_immediate(inst, rol64(inst->param[1].value, inst->param[2].value) & inst->param[3].value);
			}
			else if (param_is_immediate_value(inst, 2, 0))
			{
				inst->opcode = DRCUML_OP_AND;
				inst->numparams = 3;
				inst->param[2] = inst->param[3];
			}
			else if (param_is_immediate_value(inst, 3, instsizemask[inst->size]))
			{
				inst->opcode = DRCUML_OP_ROL;
				inst->numparams = 3;
			}
			else if (param_is_immediate(inst, 2) && param_is_immediate_value(inst, 3, ((U64(0xffffffffffffffff) << inst->param[2].value) & instsizemask[inst->size])))
			{
				inst->opcode = DRCUML_OP_SHL;
				inst->numparams = 3;
			}
			else if (param_is_immediate(inst, 2) && param_is_immediate_value(inst, 3, instsizemask[inst->size] >> (8 * inst->size - inst->param[2].value)))
			{
				inst->opcode = DRCUML_OP_SHR;
				inst->numparams = 3;
				inst->param[2].value = 8 * inst->size - inst->param[2].value;
			}
			break;

		/* ROLINS: convert to ROLAND if the mask is full */
		case DRCUML_OP_ROLINS:
			if (param_is_immediate_value(inst, 3, instsizemask[inst->size]))
				inst->opcode = DRCUML_OP_ROLAND;
			break;

		/* ADD: convert to MOV if immediate, or if adding 0 */
		case DRCUML_OP_ADD:
			if (param_is_immediate(inst, 1) && param_is_immediate(inst, 2))
				convert_to_mov_immediate(inst, inst->param[1].value + inst->param[2].value);
			else if (param_is_immediate_value(inst, 1, 0))
				convert_to_mov_param(inst, 2);
			else if (param_is_immediate_value(inst, 2, 0))
				convert_to_mov_param(inst, 1);
			break;

		/* SUB: convert to MOV if immediate, or if subtracting 0 */
		case DRCUML_OP_SUB:
			if (param_is_immediate(inst, 1) && param_is_immediate(inst, 2))
				convert_to_mov_immediate(inst, inst->param[1].value - inst->param[2].value);
			else if (param_is_immediate_value(inst, 2, 0))
				convert_to_mov_param(inst, 1);
			break;

		/* CMP: no-op if no flags needed, compare i0 to i0 if the parameters are equal */
		case DRCUML_OP_CMP:
			if (inst->flags == 0)
				convert_to_nop(inst);
			else if (param_equal(inst, 1, 2))
			{
				inst->param[1].type = inst->param[2].type = DRCUML_PTYPE_INT_REGISTER;
				inst->param[1].value = inst->param[2].value = DRCUML_REG_I0;
			}
			break;

		/* MULU: convert simple form to MOV if immediate, or if multiplying by 0 */
		case DRCUML_OP_MULU:
			if (param_equal(inst, 0, 1))
			{
				if (param_is_immediate_value(inst, 2, 0) || param_is_immediate_value(inst, 3, 0))
					convert_to_mov_immediate(inst, 0);
				else if (param_is_immediate(inst, 2) && param_is_immediate(inst, 3))
				{
					if (inst->size == 4)
						convert_to_mov_immediate(inst, (UINT32)((UINT32)inst->param[1].value * (UINT32)inst->param[2].value));
					else if (inst->size == 8)
						convert_to_mov_immediate(inst, (UINT64)((UINT64)inst->param[1].value * (UINT64)inst->param[2].value));
				}
			}
			break;

		/* MULS: convert simple form to MOV if immediate, or if multiplying by 0 */
		case DRCUML_OP_MULS:
			if (param_equal(inst, 0, 1))
			{
				if (param_is_immediate_value(inst, 2, 0) || param_is_immediate_value(inst, 3, 0))
					convert_to_mov_immediate(inst, 0);
				else if (param_is_immediate(inst, 2) && param_is_immediate(inst, 3))
				{
					if (inst->size == 4)
						convert_to_mov_immediate(inst, (INT32)((INT32)inst->param[1].value * (INT32)inst->param[2].value));
					else if (inst->size == 8)
						convert_to_mov_immediate(inst, (INT64)((INT64)inst->param[1].value * (INT64)inst->param[2].value));
				}
			}
			break;

		/* DIVU: convert simple form to MOV if immediate, or if dividing with 0 */
		case DRCUML_OP_DIVU:
			if (param_equal(inst, 0, 1) && !param_is_immediate_value(inst, 3, 0))
			{
				if (param_is_immediate_value(inst, 2, 0))
					convert_to_mov_immediate(inst, 0);
				else if (param_is_immediate(inst, 2) && param_is_immediate(inst, 3))
				{
					if (inst->size == 4)
						convert_to_mov_immediate(inst, (UINT32)((UINT32)inst->param[1].value / (UINT32)inst->param[2].value));
					else if (inst->size == 8)
						convert_to_mov_immediate(inst, (UINT64)((UINT64)inst->param[1].value / (UINT64)inst->param[2].value));
				}
			}
			break;

		/* DIVS: convert simple form to MOV if immediate, or if dividing with 0 */
		case DRCUML_OP_DIVS:
			if (param_equal(inst, 0, 1) && !param_is_immediate_value(inst, 3, 0))
			{
				if (param_is_immediate_value(inst, 2, 0))
					convert_to_mov_immediate(inst, 0);
				else if (param_is_immediate(inst, 2) && param_is_immediate(inst, 3))
				{
					if (inst->size == 4)
						convert_to_mov_immediate(inst, (INT32)((INT32)inst->param[1].value / (INT32)inst->param[2].value));
					else if (inst->size == 8)
						convert_to_mov_immediate(inst, (INT64)((INT64)inst->param[1].value / (INT64)inst->param[2].value));
				}
			}
			break;

		/* AND: convert to MOV if immediate, or if anding against 0 or 0xffffffff */
		case DRCUML_OP_AND:
			if (param_is_immediate_value(inst, 1, 0) || param_is_immediate_value(inst, 2, 0))
				convert_to_mov_immediate(inst, 0);
			else if (param_is_immediate(inst, 1) && param_is_immediate(inst, 2))
				convert_to_mov_immediate(inst, inst->param[1].value & inst->param[2].value);
			else if (param_is_immediate_value(inst, 1, instsizemask[inst->size]))
				convert_to_mov_param(inst, 2);
			else if (param_is_immediate_value(inst, 2, instsizemask[inst->size]))
				convert_to_mov_param(inst, 1);
			break;

		/* TEST: no-op if no flags needed */
		case DRCUML_OP_TEST:
			if (inst->flags == 0)
				convert_to_nop(inst);
			break;

		/* OR: convert to MOV if immediate, or if oring against 0 or 0xffffffff */
		case DRCUML_OP_OR:
			if (param_is_immediate_value(inst, 1, instsizemask[inst->size]) || param_is_immediate_value(inst, 2, instsizemask[inst->size]))
				convert_to_mov_immediate(inst, instsizemask[inst->size]);
			else if (param_is_immediate(inst, 1) && param_is_immediate(inst, 2))
				convert_to_mov_immediate(inst, inst->param[1].value | inst->param[2].value);
			else if (param_is_immediate_value(inst, 1, 0))
				convert_to_mov_param(inst, 2);
			else if (param_is_immediate_value(inst, 2, 0))
				convert_to_mov_param(inst, 1);
			break;

		/* XOR: convert to MOV if immediate, or if xoring against 0 */
		case DRCUML_OP_XOR:
			if (param_is_immediate(inst, 1) && param_is_immediate(inst, 2))
				convert_to_mov_immediate(inst, inst->param[1].value ^ inst->param[2].value);
			else if (param_is_immediate_value(inst, 1, 0))
				convert_to_mov_param(inst, 2);
			else if (param_is_immediate_value(inst, 2, 0))
				convert_to_mov_param(inst, 1);
			break;

		/* LZCNT: convert to MOV if immediate */
		case DRCUML_OP_LZCNT:
			if (param_is_immediate(inst, 1))
			{
				if (inst->size == 4)
					convert_to_mov_immediate(inst, count_leading_zeros(inst->param[1].value));
				else if (inst->size == 8)
				{
					if ((inst->param[1].value >> 32) == 0)
						convert_to_mov_immediate(inst, 32 + count_leading_zeros(inst->param[1].value));
					else
						convert_to_mov_immediate(inst, count_leading_zeros(inst->param[1].value >> 32));
				}
			}
			break;

		/* BSWAP: convert to MOV if immediate */
		case DRCUML_OP_BSWAP:
			if (param_is_immediate(inst, 1))
			{
				if (inst->size == 4)
					convert_to_mov_immediate(inst, FLIPENDIAN_INT32(inst->param[1].value));
				else if (inst->size == 8)
					convert_to_mov_immediate(inst, FLIPENDIAN_INT64(inst->param[1].value));
			}
			break;

		/* SHL: convert to MOV if immediate or shifting by 0 */
		case DRCUML_OP_SHL:
			if (param_is_immediate(inst, 1) && param_is_immediate(inst, 2))
				convert_to_mov_immediate(inst, inst->param[1].value << inst->param[2].value);
			else if (param_is_immediate_value(inst, 2, 0))
				convert_to_mov_param(inst, 1);
			break;

		/* SHR: convert to MOV if immediate or shifting by 0 */
		case DRCUML_OP_SHR:
			if (param_is_immediate(inst, 1) && param_is_immediate(inst, 2))
			{
				if (inst->size == 4)
					convert_to_mov_immediate(inst, (UINT32)inst->param[1].value >> inst->param[2].value);
				else if (inst->size == 8)
					convert_to_mov_immediate(inst, (UINT64)inst->param[1].value >> inst->param[2].value);
			}
			else if (param_is_immediate_value(inst, 2, 0))
				convert_to_mov_param(inst, 1);
			break;

		/* SAR: convert to MOV if immediate or shifting by 0 */
		case DRCUML_OP_SAR:
			if (param_is_immediate(inst, 1) && param_is_immediate(inst, 2))
			{
				if (inst->size == 4)
					convert_to_mov_immediate(inst, (INT32)inst->param[1].value >> inst->param[2].value);
				else if (inst->size == 8)
					convert_to_mov_immediate(inst, (INT64)inst->param[1].value >> inst->param[2].value);
			}
			else if (param_is_immediate_value(inst, 2, 0))
				convert_to_mov_param(inst, 1);
			break;

		/* ROL: convert to NOP if immediate or rotating by 0 */
		case DRCUML_OP_ROL:
			if (param_is_immediate(inst, 1) && param_is_immediate(inst, 2))
			{
				if (inst->size == 4)
					convert_to_mov_immediate(inst, rol32(inst->param[1].value, inst->param[2].value));
				else if (inst->size == 8)
					convert_to_mov_immediate(inst, rol64(inst->param[1].value, inst->param[2].value));
			}
			else if (param_is_immediate_value(inst, 2, 0))
				convert_to_mov_param(inst, 1);
			break;

		/* ROR: convert to NOP if immediate or rotating by 0 */
		case DRCUML_OP_ROR:
			if (param_is_immediate(inst, 1) && param_is_immediate(inst, 2))
			{
				if (inst->size == 4)
					convert_to_mov_immediate(inst, rol32(inst->param[1].value, 32 - inst->param[2].value));
				else if (inst->size == 8)
					convert_to_mov_immediate(inst, rol64(inst->param[1].value, 64 - inst->param[2].value));
			}
			else if (param_is_immediate_value(inst, 2, 0))
				convert_to_mov_param(inst, 1);
			break;

		/* FMOV: convert to NOP if move-to-self */
		case DRCUML_OP_FMOV:
			if (param_equal(inst, 0, 1))
				convert_to_nop(inst);
			break;

		default:
			break;
	}

	if (LOG_SIMPLIFICATIONS && memcmp(&orig, inst, sizeof(orig)) != 0)
	{
		char disasm1[256], disasm2[256];
		drcuml_disasm(&orig, disasm1, block->drcuml);
		drcuml_disasm(inst, disasm2, block->drcuml);
		mame_printf_debug("Simplified: %-50.50s -> %s\n", disasm1, disasm2);
	}

	/* if the opcode changed, validate and reoptimize */
	if (inst->opcode != origop)
	{
		validate_instruction(block, inst);
		simplify_instruction_with_no_flags(block, inst);
	}
}



/***************************************************************************
    LOGGING HELPERS
***************************************************************************/

/*-------------------------------------------------
    disassemble_block - disassemble a block of
    instructions to the log
-------------------------------------------------*/

static void disassemble_block(drcuml_block *block)
{
	int firstcomment = -1;
	int instnum;

	/* iterate over instructions and output */
	for (instnum = 0; instnum < block->nextinst; instnum++)
	{
		const drcuml_instruction *inst = &block->inst[instnum];
		int flushcomments = FALSE;

		/* remember comments and mapvars for later */
		if (inst->opcode == DRCUML_OP_COMMENT || inst->opcode == DRCUML_OP_MAPVAR)
		{
			if (firstcomment == -1)
				firstcomment = instnum;
		}

		/* print labels, handles, and hashes left justified */
		else if (inst->opcode == DRCUML_OP_LABEL)
			drcuml_log_printf(block->drcuml, "$%X:\n", (UINT32)inst->param[0].value);
		else if (inst->opcode == DRCUML_OP_HANDLE)
			drcuml_log_printf(block->drcuml, "%s:\n", drcuml_handle_name((const drcuml_codehandle *)(FPTR)inst->param[0].value));
		else if (inst->opcode == DRCUML_OP_HASH)
			drcuml_log_printf(block->drcuml, "(%X,%X):\n", (UINT32)inst->param[0].value, (UINT32)inst->param[1].value);

		/* indent everything else with a tab */
		else
		{
			char dasm[256];
			drcuml_disasm(&block->inst[instnum], dasm, block->drcuml);

			/* include the first accumulated comment with this line */
			if (firstcomment != -1)
			{
				drcuml_log_printf(block->drcuml, "\t%-50.50s; %s\n", dasm, get_comment_text(&block->inst[firstcomment]));
				firstcomment++;
				flushcomments = TRUE;
			}
			else
				drcuml_log_printf(block->drcuml, "\t%s\n", dasm);
		}

		/* flush any comments pending */
		if (firstcomment != -1 && (flushcomments || instnum == block->nextinst - 1))
		{
			while (firstcomment <= instnum)
			{
				const char *text = get_comment_text(&block->inst[firstcomment++]);
				if (text != NULL)
					drcuml_log_printf(block->drcuml, "\t%50s; %s\n", "", text);
			}
			firstcomment = -1;
		}
	}
	drcuml_log_printf(block->drcuml, "\n\n");
	fflush(block->drcuml->umllog);
}


/*-------------------------------------------------
    get_comment_text - determine the text
    associated with a comment or mapvar
-------------------------------------------------*/

static const char *get_comment_text(const drcuml_instruction *inst)
{
	static char tempbuf[100];

	/* comments return their strings */
	if (inst->opcode == DRCUML_OP_COMMENT)
		return (char *)(FPTR)inst->param[0].value;

	/* mapvars comment about their values */
	else if (inst->opcode == DRCUML_OP_MAPVAR)
	{
		sprintf(tempbuf, "m%d = $%X", (int)inst->param[0].value - DRCUML_MAPVAR_M0, (UINT32)inst->param[1].value);
		return tempbuf;
	}

	/* everything else is NULL */
	return NULL;
}



/***************************************************************************
    OPCODE VALIDATION
***************************************************************************/

/*-------------------------------------------------
    validate_instruction - verify that the
    instruction created meets all requirements
-------------------------------------------------*/

static void validate_instruction(drcuml_block *block, const drcuml_instruction *inst)
{
	const drcuml_opcode_info *opinfo = opcode_info_table[inst->opcode];
	int pnum;

	/* validate raw information */
	assert(inst->opcode != DRCUML_OP_INVALID && inst->opcode < DRCUML_OP_MAX);
	assert(inst->size == 1 || inst->size == 2 || inst->size == 4 || inst->size == 8);

	/* validate against opcode limits */
	assert((opinfo->sizes & inst->size) != 0);
	assert(inst->condition == DRCUML_COND_ALWAYS || opinfo->condition);

	/* validate each parameter */
	for (pnum = 0; pnum < inst->numparams; pnum++)
	{
		const drcuml_parameter *param = &inst->param[pnum];
		UINT16 typemask = opinfo->param[pnum].typemask;

		/* ensure the type is correct */
		assert(param->type > DRCUML_PTYPE_NONE && param->type < DRCUML_PTYPE_MAX);
		assert((typemask >> param->type) & 1);

		/* validate various parameter types */
		switch (param->type)
		{
			case DRCUML_PTYPE_MEMORY:
				/* most memory parameters must be in the near cache */
				if (typemask != PTYPES_PTR && typemask != PTYPES_STATE && typemask != PTYPES_STR && typemask != PTYPES_CFUNC)
					assert_in_near_cache(block->drcuml->cache, (void *)(FPTR)param->value);
				break;

			case DRCUML_PTYPE_IMMEDIATE:
				/* many special parameter types are encoded as immediately; ensure they are in range */
				if (typemask == PTYPES_SIZE)
					assert(param->value >= DRCUML_SIZE_BYTE && param->value <= DRCUML_SIZE_QWORD);
				else if (typemask == PTYPES_SPACE)
					assert(param->value >= ADDRESS_SPACE_PROGRAM && param->value <= ADDRESS_SPACE_IO);
				else if (typemask == PTYPES_SPSZ)
				{
					assert(param->value % 16 >= DRCUML_SIZE_BYTE && param->value % 16 <= DRCUML_SIZE_QWORD);
					assert(param->value / 16 >= ADDRESS_SPACE_PROGRAM && param->value / 16 <= ADDRESS_SPACE_IO);
				}
				else if (typemask == PTYPES_FMOD)
					assert(param->value >= DRCUML_FMOD_TRUNC && param->value <= DRCUML_FMOD_DEFAULT);
				break;

			case DRCUML_PTYPE_MAPVAR:
				assert(param->value >= DRCUML_MAPVAR_M0 && param->value < DRCUML_MAPVAR_END);
				break;

			case DRCUML_PTYPE_INT_REGISTER:
				assert(param->value >= DRCUML_REG_I0 && param->value < DRCUML_REG_I_END);
				break;

			case DRCUML_PTYPE_FLOAT_REGISTER:
				assert(param->value >= DRCUML_REG_F0 && param->value < DRCUML_REG_F_END);
				break;

			default:
				assert(FALSE);
				break;
		}
	}

	/* make sure we aren't missing any parameters */
	if (inst->numparams < ARRAY_LENGTH(opinfo->param))
		assert(opinfo->param[inst->numparams].typemask == 0);
}



/***************************************************************************
    BACK-END VALIDATION
***************************************************************************/

#define TEST_ENTRY_2(op, size, p1, p2, flags) { DRCUML_OP_##op, size, 0, flags, { U64(p1), U64(p2) } },
#define TEST_ENTRY_2F(op, size, p1, p2, iflags, flags) { DRCUML_OP_##op, size, iflags, flags, { U64(p1), U64(p2) } },
#define TEST_ENTRY_3(op, size, p1, p2, p3, flags) { DRCUML_OP_##op, size, 0, flags, { U64(p1), U64(p2), U64(p3) } },
#define TEST_ENTRY_3F(op, size, p1, p2, p3, iflags, flags) { DRCUML_OP_##op, size, iflags, flags, { U64(p1), U64(p2), U64(p3) } },
#define TEST_ENTRY_4(op, size, p1, p2, p3, p4, flags) { DRCUML_OP_##op, size, 0, flags, { U64(p1), U64(p2), U64(p3), U64(p4) } },
#define TEST_ENTRY_4F(op, size, p1, p2, p3, p4, iflags, flags) { DRCUML_OP_##op, size, iflags, flags, { U64(p1), U64(p2), U64(p3), U64(p4) } },

static const bevalidate_test bevalidate_test_list[] =
{
	TEST_ENTRY_3(ADD, 4, 0x7fffffff, 0x12345678, 0x6dcba987, 0)
	TEST_ENTRY_3(ADD, 4, 0x80000000, 0x12345678, 0x6dcba988, DRCUML_FLAG_V | DRCUML_FLAG_S)
	TEST_ENTRY_3(ADD, 4, 0xffffffff, 0x92345678, 0x6dcba987, DRCUML_FLAG_S)
	TEST_ENTRY_3(ADD, 4, 0x00000000, 0x92345678, 0x6dcba988, DRCUML_FLAG_C | DRCUML_FLAG_Z)

	TEST_ENTRY_3(ADD, 8, 0x7fffffffffffffff, 0x0123456789abcdef, 0x7edcba9876543210, 0)
	TEST_ENTRY_3(ADD, 8, 0x8000000000000000, 0x0123456789abcdef, 0x7edcba9876543211, DRCUML_FLAG_V | DRCUML_FLAG_S)
	TEST_ENTRY_3(ADD, 8, 0xffffffffffffffff, 0x8123456789abcdef, 0x7edcba9876543210, DRCUML_FLAG_S)
	TEST_ENTRY_3(ADD, 8, 0x0000000000000000, 0x8123456789abcdef, 0x7edcba9876543211, DRCUML_FLAG_C | DRCUML_FLAG_Z)

	TEST_ENTRY_3F(ADDC, 4, 0x7fffffff, 0x12345678, 0x6dcba987, 0,       0)
	TEST_ENTRY_3F(ADDC, 4, 0x7fffffff, 0x12345678, 0x6dcba986, DRCUML_FLAG_C, 0)
	TEST_ENTRY_3F(ADDC, 4, 0x80000000, 0x12345678, 0x6dcba988, 0,             DRCUML_FLAG_V | DRCUML_FLAG_S)
	TEST_ENTRY_3F(ADDC, 4, 0x80000000, 0x12345678, 0x6dcba987, DRCUML_FLAG_C, DRCUML_FLAG_V | DRCUML_FLAG_S)
	TEST_ENTRY_3F(ADDC, 4, 0xffffffff, 0x92345678, 0x6dcba987, 0,             DRCUML_FLAG_S)
	TEST_ENTRY_3F(ADDC, 4, 0xffffffff, 0x92345678, 0x6dcba986, DRCUML_FLAG_C, DRCUML_FLAG_S)
	TEST_ENTRY_3F(ADDC, 4, 0x00000000, 0x92345678, 0x6dcba988, 0,             DRCUML_FLAG_C | DRCUML_FLAG_Z)
	TEST_ENTRY_3F(ADDC, 4, 0x00000000, 0x92345678, 0x6dcba987, DRCUML_FLAG_C, DRCUML_FLAG_C | DRCUML_FLAG_Z)
	TEST_ENTRY_3F(ADDC, 4, 0x12345678, 0x12345678, 0xffffffff, DRCUML_FLAG_C, DRCUML_FLAG_C)

	TEST_ENTRY_3F(ADDC, 8, 0x7fffffffffffffff, 0x0123456789abcdef, 0x7edcba9876543210, 0,             0)
	TEST_ENTRY_3F(ADDC, 8, 0x7fffffffffffffff, 0x0123456789abcdef, 0x7edcba987654320f, DRCUML_FLAG_C, 0)
	TEST_ENTRY_3F(ADDC, 8, 0x8000000000000000, 0x0123456789abcdef, 0x7edcba9876543211, 0,             DRCUML_FLAG_V | DRCUML_FLAG_S)
	TEST_ENTRY_3F(ADDC, 8, 0x8000000000000000, 0x0123456789abcdef, 0x7edcba9876543210, DRCUML_FLAG_C, DRCUML_FLAG_V | DRCUML_FLAG_S)
	TEST_ENTRY_3F(ADDC, 8, 0xffffffffffffffff, 0x8123456789abcdef, 0x7edcba9876543210, 0,             DRCUML_FLAG_S)
	TEST_ENTRY_3F(ADDC, 8, 0xffffffffffffffff, 0x8123456789abcdef, 0x7edcba987654320f, DRCUML_FLAG_C, DRCUML_FLAG_S)
	TEST_ENTRY_3F(ADDC, 8, 0x0000000000000000, 0x8123456789abcdef, 0x7edcba9876543211, 0,             DRCUML_FLAG_C | DRCUML_FLAG_Z)
	TEST_ENTRY_3F(ADDC, 8, 0x0000000000000000, 0x8123456789abcdef, 0x7edcba9876543210, DRCUML_FLAG_C, DRCUML_FLAG_C | DRCUML_FLAG_Z)
	TEST_ENTRY_3F(ADDC, 8, 0x123456789abcdef0, 0x123456789abcdef0, 0xffffffffffffffff, DRCUML_FLAG_C, DRCUML_FLAG_C)

	TEST_ENTRY_3(SUB, 4, 0x12345678, 0x7fffffff, 0x6dcba987, 0)
	TEST_ENTRY_3(SUB, 4, 0x12345678, 0x80000000, 0x6dcba988, DRCUML_FLAG_V)
	TEST_ENTRY_3(SUB, 4, 0x92345678, 0xffffffff, 0x6dcba987, DRCUML_FLAG_S)
	TEST_ENTRY_3(SUB, 4, 0x92345678, 0x00000000, 0x6dcba988, DRCUML_FLAG_C | DRCUML_FLAG_S)
	TEST_ENTRY_3(SUB, 4, 0x00000000, 0x12345678, 0x12345678, DRCUML_FLAG_Z)

	TEST_ENTRY_3(SUB, 8, 0x0123456789abcdef, 0x7fffffffffffffff, 0x7edcba9876543210, 0)
	TEST_ENTRY_3(SUB, 8, 0x0123456789abcdef, 0x8000000000000000, 0x7edcba9876543211, DRCUML_FLAG_V)
	TEST_ENTRY_3(SUB, 8, 0x8123456789abcdef, 0xffffffffffffffff, 0x7edcba9876543210, DRCUML_FLAG_S)
	TEST_ENTRY_3(SUB, 8, 0x8123456789abcdef, 0x0000000000000000, 0x7edcba9876543211, DRCUML_FLAG_C | DRCUML_FLAG_S)
	TEST_ENTRY_3(SUB, 8, 0x0000000000000000, 0x0123456789abcdef, 0x0123456789abcdef, DRCUML_FLAG_Z)

	TEST_ENTRY_3F(SUBB, 4, 0x12345678, 0x7fffffff, 0x6dcba987, 0,             0)
	TEST_ENTRY_3F(SUBB, 4, 0x12345678, 0x7fffffff, 0x6dcba986, DRCUML_FLAG_C, 0)
	TEST_ENTRY_3F(SUBB, 4, 0x12345678, 0x80000000, 0x6dcba988, 0,             DRCUML_FLAG_V)
	TEST_ENTRY_3F(SUBB, 4, 0x12345678, 0x80000000, 0x6dcba987, DRCUML_FLAG_C, DRCUML_FLAG_V)
	TEST_ENTRY_3F(SUBB, 4, 0x92345678, 0xffffffff, 0x6dcba987, 0,             DRCUML_FLAG_S)
	TEST_ENTRY_3F(SUBB, 4, 0x92345678, 0xffffffff, 0x6dcba986, DRCUML_FLAG_C, DRCUML_FLAG_S)
	TEST_ENTRY_3F(SUBB, 4, 0x92345678, 0x00000000, 0x6dcba988, 0,             DRCUML_FLAG_C | DRCUML_FLAG_S)
	TEST_ENTRY_3F(SUBB, 4, 0x92345678, 0x00000000, 0x6dcba987, DRCUML_FLAG_C, DRCUML_FLAG_C | DRCUML_FLAG_S)
	TEST_ENTRY_3F(SUBB, 4, 0x12345678, 0x12345678, 0xffffffff, DRCUML_FLAG_C, DRCUML_FLAG_C)
	TEST_ENTRY_3F(SUBB, 4, 0x00000000, 0x12345678, 0x12345677, DRCUML_FLAG_C, DRCUML_FLAG_Z)

	TEST_ENTRY_3F(SUBB, 8, 0x0123456789abcdef, 0x7fffffffffffffff, 0x7edcba9876543210, 0,             0)
	TEST_ENTRY_3F(SUBB, 8, 0x0123456789abcdef, 0x7fffffffffffffff, 0x7edcba987654320f, DRCUML_FLAG_C, 0)
	TEST_ENTRY_3F(SUBB, 8, 0x0123456789abcdef, 0x8000000000000000, 0x7edcba9876543211, 0,             DRCUML_FLAG_V)
	TEST_ENTRY_3F(SUBB, 8, 0x0123456789abcdef, 0x8000000000000000, 0x7edcba9876543210, DRCUML_FLAG_C, DRCUML_FLAG_V)
	TEST_ENTRY_3F(SUBB, 8, 0x8123456789abcdef, 0xffffffffffffffff, 0x7edcba9876543210, 0,             DRCUML_FLAG_S)
	TEST_ENTRY_3F(SUBB, 8, 0x8123456789abcdef, 0xffffffffffffffff, 0x7edcba987654320f, DRCUML_FLAG_C, DRCUML_FLAG_S)
	TEST_ENTRY_3F(SUBB, 8, 0x8123456789abcdef, 0x0000000000000000, 0x7edcba9876543211, 0,             DRCUML_FLAG_C | DRCUML_FLAG_S)
	TEST_ENTRY_3F(SUBB, 8, 0x8123456789abcdef, 0x0000000000000000, 0x7edcba9876543210, DRCUML_FLAG_C, DRCUML_FLAG_C | DRCUML_FLAG_S)
	TEST_ENTRY_3F(SUBB, 8, 0x123456789abcdef0, 0x123456789abcdef0, 0xffffffffffffffff, DRCUML_FLAG_C, DRCUML_FLAG_C)
	TEST_ENTRY_3F(SUBB, 8, 0x0000000000000000, 0x123456789abcdef0, 0x123456789abcdeef, DRCUML_FLAG_C, DRCUML_FLAG_Z)

	TEST_ENTRY_2(CMP, 4, 0x7fffffff, 0x6dcba987, 0)
	TEST_ENTRY_2(CMP, 4, 0x80000000, 0x6dcba988, DRCUML_FLAG_V)
	TEST_ENTRY_2(CMP, 4, 0xffffffff, 0x6dcba987, DRCUML_FLAG_S)
	TEST_ENTRY_2(CMP, 4, 0x00000000, 0x6dcba988, DRCUML_FLAG_C | DRCUML_FLAG_S)
	TEST_ENTRY_2(CMP, 4, 0x12345678, 0x12345678, DRCUML_FLAG_Z)

	TEST_ENTRY_2(CMP, 8, 0x7fffffffffffffff, 0x7edcba9876543210, 0)
	TEST_ENTRY_2(CMP, 8, 0x8000000000000000, 0x7edcba9876543211, DRCUML_FLAG_V)
	TEST_ENTRY_2(CMP, 8, 0xffffffffffffffff, 0x7edcba9876543210, DRCUML_FLAG_S)
	TEST_ENTRY_2(CMP, 8, 0x0000000000000000, 0x7edcba9876543211, DRCUML_FLAG_C | DRCUML_FLAG_S)
	TEST_ENTRY_2(CMP, 8, 0x0123456789abcdef, 0x0123456789abcdef, DRCUML_FLAG_Z)

	TEST_ENTRY_4(MULU, 4, 0x77777777, 0x00000000, 0x11111111, 0x00000007, 0)
	TEST_ENTRY_4(MULU, 4, 0xffffffff, 0x00000000, 0x11111111, 0x0000000f, 0)
	TEST_ENTRY_4(MULU, 4, 0x00000000, 0x00000000, 0x11111111, 0x00000000, DRCUML_FLAG_Z)
	TEST_ENTRY_4(MULU, 4, 0xea61d951, 0x37c048d0, 0x77777777, 0x77777777, DRCUML_FLAG_V)
	TEST_ENTRY_4(MULU, 4, 0x32323233, 0xcdcdcdcc, 0xcdcdcdcd, 0xffffffff, DRCUML_FLAG_V | DRCUML_FLAG_S)

	TEST_ENTRY_4(MULU, 8, 0x7777777777777777, 0x0000000000000000, 0x1111111111111111, 0x0000000000000007, 0)
	TEST_ENTRY_4(MULU, 8, 0xffffffffffffffff, 0x0000000000000000, 0x1111111111111111, 0x000000000000000f, 0)
	TEST_ENTRY_4(MULU, 8, 0x0000000000000000, 0x0000000000000000, 0x1111111111111111, 0x0000000000000000, DRCUML_FLAG_Z)
	TEST_ENTRY_4(MULU, 8, 0x0c83fb72ea61d951, 0x37c048d159e26af3, 0x7777777777777777, 0x7777777777777777, DRCUML_FLAG_V)
	TEST_ENTRY_4(MULU, 8, 0x3232323232323233, 0xcdcdcdcdcdcdcdcc, 0xcdcdcdcdcdcdcdcd, 0xffffffffffffffff, DRCUML_FLAG_V | DRCUML_FLAG_S)

	TEST_ENTRY_4(MULS, 4, 0x77777777, 0x00000000, 0x11111111, 0x00000007, 0)
	TEST_ENTRY_4(MULS, 4, 0xffffffff, 0x00000000, 0x11111111, 0x0000000f, DRCUML_FLAG_V)
	TEST_ENTRY_4(MULS, 4, 0x00000000, 0x00000000, 0x11111111, 0x00000000, DRCUML_FLAG_Z)
	TEST_ENTRY_4(MULS, 4, 0x9e26af38, 0xc83fb72e, 0x77777777, 0x88888888, DRCUML_FLAG_V | DRCUML_FLAG_S)
	TEST_ENTRY_4(MULS, 4, 0x32323233, 0x00000000, 0xcdcdcdcd, 0xffffffff, 0)

	TEST_ENTRY_4(MULS, 8, 0x7777777777777777, 0x0000000000000000, 0x1111111111111111, 0x0000000000000007, 0)
	TEST_ENTRY_4(MULS, 8, 0xffffffffffffffff, 0x0000000000000000, 0x1111111111111111, 0x000000000000000f, DRCUML_FLAG_V)
	TEST_ENTRY_4(MULS, 8, 0x0000000000000000, 0x0000000000000000, 0x1111111111111111, 0x0000000000000000, DRCUML_FLAG_Z)
	TEST_ENTRY_4(MULS, 8, 0x7c048d159e26af38, 0xc83fb72ea61d950c, 0x7777777777777777, 0x8888888888888888, DRCUML_FLAG_V | DRCUML_FLAG_S)
	TEST_ENTRY_4(MULS, 8, 0x3232323232323233, 0x0000000000000000, 0xcdcdcdcdcdcdcdcd, 0xffffffffffffffff, 0)

	TEST_ENTRY_4(DIVU, 4, 0x02702702, 0x00000003, 0x11111111, 0x00000007, 0)
	TEST_ENTRY_4(DIVU, 4, 0x00000000, 0x11111111, 0x11111111, 0x11111112, DRCUML_FLAG_Z)
	TEST_ENTRY_4(DIVU, 4, 0x7fffffff, 0x00000000, 0xfffffffe, 0x00000002, 0)
	TEST_ENTRY_4(DIVU, 4, 0xfffffffe, 0x00000000, 0xfffffffe, 0x00000001, DRCUML_FLAG_S)
	TEST_ENTRY_4(DIVU, 4, UNDEFINED,  UNDEFINED,  0xffffffff, 0x00000000, DRCUML_FLAG_V)

	TEST_ENTRY_4(DIVU, 8, 0x0270270270270270, 0x0000000000000001, 0x1111111111111111, 0x0000000000000007, 0)
	TEST_ENTRY_4(DIVU, 8, 0x0000000000000000, 0x1111111111111111, 0x1111111111111111, 0x1111111111111112, DRCUML_FLAG_Z)
	TEST_ENTRY_4(DIVU, 8, 0x7fffffffffffffff, 0x0000000000000000, 0xfffffffffffffffe, 0x0000000000000002, 0)
	TEST_ENTRY_4(DIVU, 8, 0xfffffffffffffffe, 0x0000000000000000, 0xfffffffffffffffe, 0x0000000000000001, DRCUML_FLAG_S)
	TEST_ENTRY_4(DIVU, 8, UNDEFINED,          UNDEFINED,          0xffffffffffffffff, 0x0000000000000000, DRCUML_FLAG_V)

	TEST_ENTRY_4(DIVS, 4, 0x02702702, 0x00000003, 0x11111111, 0x00000007, 0)
	TEST_ENTRY_4(DIVS, 4, 0x00000000, 0x11111111, 0x11111111, 0x11111112, DRCUML_FLAG_Z)
	TEST_ENTRY_4(DIVS, 4, 0xffffffff, 0x00000000, 0xfffffffe, 0x00000002, DRCUML_FLAG_S)
	TEST_ENTRY_4(DIVS, 4, UNDEFINED,  UNDEFINED,  0xffffffff, 0x00000000, DRCUML_FLAG_V)

	TEST_ENTRY_4(DIVS, 8, 0x0270270270270270, 0x0000000000000001, 0x1111111111111111, 0x0000000000000007, 0)
	TEST_ENTRY_4(DIVS, 8, 0x0000000000000000, 0x1111111111111111, 0x1111111111111111, 0x1111111111111112, DRCUML_FLAG_Z)
	TEST_ENTRY_4(DIVS, 8, 0xffffffffffffffff, 0x0000000000000000, 0xfffffffffffffffe, 0x0000000000000002, DRCUML_FLAG_S)
	TEST_ENTRY_4(DIVS, 8, UNDEFINED,          UNDEFINED,          0xffffffffffffffff, 0x0000000000000000, DRCUML_FLAG_V)
};


/*-------------------------------------------------
    validate_backend - execute a number of
    generic tests on the backend code generator
-------------------------------------------------*/

static void validate_backend(drcuml_state *drcuml)
{
	drcuml_codehandle *handles[3];
	int tnum;

	/* allocate handles for the code */
	handles[0] = drcuml_handle_alloc(drcuml, "test_entry");
	handles[1] = drcuml_handle_alloc(drcuml, "code_start");
	handles[2] = drcuml_handle_alloc(drcuml, "code_end");

	/* iterate over test entries */
	printf("Backend validation....\n");
	for (tnum = 31; tnum < ARRAY_LENGTH(bevalidate_test_list); tnum++)
	{
		const bevalidate_test *test = &bevalidate_test_list[tnum];
		drcuml_parameter param[ARRAY_LENGTH(test->param)];
		char mnemonic[20], *dst;
		const char *src;

		/* progress */
		dst = mnemonic;
		for (src = opcode_info_table[test->opcode]->mnemonic; *src != 0; src++)
		{
			if (*src == '!')
			{
				if (test->size == 8)
					*dst++ = 'd';
			}
			else if (*src == '#')
				*dst++ = (test->size == 8) ? 'd' : 's';
			else
				*dst++ = *src;
		}
		*dst = 0;
		printf("Executing test %d/%d (%s)", tnum + 1, (int)ARRAY_LENGTH(bevalidate_test_list), mnemonic);

		/* reset parameter list and iterate */
		memset(param, 0, sizeof(param));
		bevalidate_iterate_over_params(drcuml, handles, test, param, 0);
		printf("\n");
	}
	fatalerror("All tests passed!");
}


/*-------------------------------------------------
    bevalidate_iterate_over_params - iterate over
    all supported types and values of a parameter
    and recursively hand off to the next parameter,
    or else move on to iterate over the flags
-------------------------------------------------*/

static void bevalidate_iterate_over_params(drcuml_state *drcuml, drcuml_codehandle **handles, const bevalidate_test *test, drcuml_parameter *paramlist, int pnum)
{
	const drcuml_opcode_info *opinfo = opcode_info_table[test->opcode];
	drcuml_ptype ptype;

	/* if no parameters, execute now */
	if (pnum >= ARRAY_LENGTH(opinfo->param) || opinfo->param[pnum].typemask == PTYPES_NONE)
	{
		bevalidate_iterate_over_flags(drcuml, handles, test, paramlist);
		return;
	}

	/* iterate over valid parameter types */
	for (ptype = DRCUML_PTYPE_IMMEDIATE; ptype < DRCUML_PTYPE_MAX; ptype++)
		if (opinfo->param[pnum].typemask & (1 << ptype))
		{
			int pindex, pcount;

			/* mapvars can only do 32-bit tests */
			if (ptype == DRCUML_PTYPE_MAPVAR && effective_test_psize(opinfo, pnum, test->size, test->param) == 8)
				continue;

			/* for some parameter types, we wish to iterate over all possibilities */
			switch (ptype)
			{
				case DRCUML_PTYPE_INT_REGISTER:		pcount = DRCUML_REG_I_END - DRCUML_REG_I0;		break;
				case DRCUML_PTYPE_FLOAT_REGISTER:	pcount = DRCUML_REG_F_END - DRCUML_REG_F0;		break;
				default:							pcount = 1;										break;
			}

			/* iterate over possibilities */
			for (pindex = 0; pindex < pcount; pindex++)
			{
				int skip = FALSE;
				int pscannum;

				/* for param 0, print a dot */
				if (pnum == 0)
					printf(".");

				/* can't duplicate multiple source parameters unless they are immediates */
				if (ptype != DRCUML_PTYPE_IMMEDIATE && (opinfo->param[pnum].output & PIO_IN))

					/* loop over all parameters we've done before; if the parameter is a source and matches us, skip this case */
					for (pscannum = 0; pscannum < pnum; pscannum++)
						if ((opinfo->param[pscannum].output & PIO_IN) && ptype == paramlist[pscannum].type && pindex == paramlist[pscannum].value)
							skip = TRUE;

				/* can't duplicate multiple dest parameters */
				if (opinfo->param[pnum].output & PIO_OUT)

					/* loop over all parameters we've done before; if the parameter is a source and matches us, skip this case */
					for (pscannum = 0; pscannum < pnum; pscannum++)
						if ((opinfo->param[pscannum].output & PIO_OUT) && ptype == paramlist[pscannum].type && pindex == paramlist[pscannum].value)
							skip = TRUE;

				/* iterate over the next parameter in line */
				if (!skip)
				{
					paramlist[pnum].type = ptype;
					paramlist[pnum].value = pindex;
					bevalidate_iterate_over_params(drcuml, handles, test, paramlist, pnum + 1);
				}
			}
		}
}


/*-------------------------------------------------
    bevalidate_iterate_over_flags - iterate over
    all supported flag masks
-------------------------------------------------*/

static void bevalidate_iterate_over_flags(drcuml_state *drcuml, drcuml_codehandle **handles, const bevalidate_test *test, drcuml_parameter *paramlist)
{
	const drcuml_opcode_info *opinfo = opcode_info_table[test->opcode];
	UINT8 flagmask = opinfo->outflags;
	UINT8 curmask;

	/* iterate over all possible flag combinations */
	for (curmask = 0; curmask <= flagmask; curmask++)
		if ((curmask & flagmask) == curmask)
			bevalidate_execute(drcuml, handles, test, paramlist, curmask);
}


/*-------------------------------------------------
    bevalidate_execute - execute a single instance
    of a test, generating code and verifying the
    results
-------------------------------------------------*/

static void bevalidate_execute(drcuml_state *drcuml, drcuml_codehandle **handles, const bevalidate_test *test, const drcuml_parameter *paramlist, UINT8 flagmask)
{
	drcuml_parameter params[ARRAY_LENGTH(test->param)];
	drcuml_machine_state istate, fstate;
	drcuml_instruction testinst;
	drcuml_block *block;
	UINT64 *parammem;
	int numparams;

	/* allocate memory for parameters */
	parammem = (UINT64 *)drccache_memory_alloc_near(drcuml->cache, sizeof(UINT64) * (ARRAY_LENGTH(test->param) + 1));

	/* flush the cache */
	drcuml_reset(drcuml);

	/* start a new block */
	block = drcuml_block_begin(drcuml, 30, NULL);
	UML_HANDLE(block, handles[0]);

	/* set up a random initial state */
	bevalidate_initialize_random_state(drcuml, block, &istate);

	/* then populate the state with the parameters */
	numparams = bevalidate_populate_state(block, &istate, test, paramlist, params, parammem);

	/* generate the code */
	UML_RESTORE(block, &istate);
	UML_HANDLE(block, handles[1]);
	switch (numparams)
	{
		case 0:
			drcuml_block_append_0(block, test->opcode, test->size, DRCUML_COND_ALWAYS);
			break;

		case 1:
			drcuml_block_append_1(block, test->opcode, test->size, DRCUML_COND_ALWAYS, params[0].type, params[0].value);
			break;

		case 2:
			drcuml_block_append_2(block, test->opcode, test->size, DRCUML_COND_ALWAYS, params[0].type, params[0].value, params[1].type, params[1].value);
			break;

		case 3:
			drcuml_block_append_3(block, test->opcode, test->size, DRCUML_COND_ALWAYS, params[0].type, params[0].value, params[1].type, params[1].value, params[2].type, params[2].value);
			break;

		case 4:
			drcuml_block_append_4(block, test->opcode, test->size, DRCUML_COND_ALWAYS, params[0].type, params[0].value, params[1].type, params[1].value, params[2].type, params[2].value, params[3].type, params[3].value);
			break;
	}
	testinst = block->inst[block->nextinst - 1];
	UML_HANDLE(block, handles[2]);
	UML_GETFLGS(block, MEM(&parammem[ARRAY_LENGTH(test->param)]), flagmask);
	UML_SAVE(block, &fstate);
	UML_EXIT(block, IMM(0));

	/* end the block */
	drcuml_block_end(block);

	/* execute */
	drcuml_execute(drcuml, handles[0]);

	/* verify the results */
	bevalidate_verify_state(drcuml, &istate, &fstate, test, *(UINT32 *)&parammem[ARRAY_LENGTH(test->param)], params, &testinst, handles[1]->code, handles[2]->code, flagmask);

	/* free memory */
	drccache_memory_free(drcuml->cache, parammem, sizeof(UINT64) * (ARRAY_LENGTH(test->param) + 1));
}


/*-------------------------------------------------
    bevalidate_initialize_random_state -
    initialize the machine state to randomness
-------------------------------------------------*/

static void bevalidate_initialize_random_state(drcuml_state *drcuml, drcuml_block *block, drcuml_machine_state *state)
{
	running_machine *machine = drcuml->device->machine;
	int regnum;

	/* initialize core state to random values */
	state->fmod = mame_rand(machine) & 0x03;
	state->flags = mame_rand(machine) & 0x1f;
	state->exp = mame_rand(machine);

	/* initialize integer registers to random values */
	for (regnum = 0; regnum < ARRAY_LENGTH(state->r); regnum++)
	{
		state->r[regnum].w.h = mame_rand(machine);
		state->r[regnum].w.l = mame_rand(machine);
	}

	/* initialize float registers to random values */
	for (regnum = 0; regnum < ARRAY_LENGTH(state->f); regnum++)
	{
		*(UINT32 *)&state->f[regnum].s.h = mame_rand(machine);
		*(UINT32 *)&state->f[regnum].s.l = mame_rand(machine);
	}

	/* initialize map variables to random values */
	for (regnum = 0; regnum < DRCUML_MAPVAR_END - DRCUML_MAPVAR_M0; regnum++)
		UML_MAPVAR(block, MVAR(regnum), mame_rand(machine));
}


/*-------------------------------------------------
    bevalidate_populate_state - populate the
    machine state with the proper values prior
    to executing a test
-------------------------------------------------*/

static int bevalidate_populate_state(drcuml_block *block, drcuml_machine_state *state, const bevalidate_test *test, const drcuml_parameter *paramlist, drcuml_parameter *params, UINT64 *parammem)
{
	const drcuml_opcode_info *opinfo = opcode_info_table[test->opcode];
	int numparams = ARRAY_LENGTH(test->param);
	int pnum;

	/* copy flags as-is */
	state->flags = test->iflags;

	/* iterate over parameters */
	for (pnum = 0; pnum < ARRAY_LENGTH(test->param); pnum++)
	{
		int psize = effective_test_psize(opinfo, pnum, test->size, test->param);
		drcuml_parameter *curparam = &params[pnum];

		/* start with a copy of the parameter from the list */
		*curparam = paramlist[pnum];

		/* switch off the type */
		switch (curparam->type)
		{
			/* immediate parameters: take the value from the test entry */
			case DRCUML_PTYPE_IMMEDIATE:
				curparam->value = test->param[pnum];
				break;

			/* register parameters: set the register value in the state and set the parameter value to the register index */
			case DRCUML_PTYPE_INT_REGISTER:
				state->r[curparam->value].d = test->param[pnum];
				curparam->value += DRCUML_REG_I0;
				break;

			/* register parameters: set the register value in the state and set the parameter value to the register index */
			case DRCUML_PTYPE_FLOAT_REGISTER:
				state->f[curparam->value].d = test->param[pnum];
				curparam->value += DRCUML_REG_F0;
				break;

			/* memory parameters: set the memory value in the parameter space and set the parameter value to point to it */
			case DRCUML_PTYPE_MEMORY:
				curparam->value = (FPTR)&parammem[pnum];
				if (psize == 4)
					*(UINT32 *)(FPTR)curparam->value = test->param[pnum];
				else
					*(UINT64 *)(FPTR)curparam->value = test->param[pnum];
				break;

			/* map variables: issue a MAPVAR instruction to set the value and set the parameter value to the mapvar index */
			case DRCUML_PTYPE_MAPVAR:
				UML_MAPVAR(block, MVAR(curparam->value), test->param[pnum]);
				curparam->value += DRCUML_MAPVAR_M0;
				break;

			/* use anything else to count the number of parameters */
			default:
				numparams = MIN(numparams, pnum);
				break;
		}
	}

	/* return the total number of parameters */
	return numparams;
}


/*-------------------------------------------------
    bevalidate_verify_state - verify the final
    state after executing a test, and report any
    discrepancies
-------------------------------------------------*/

static int bevalidate_verify_state(drcuml_state *drcuml, const drcuml_machine_state *istate, drcuml_machine_state *state, const bevalidate_test *test, UINT32 flags, const drcuml_parameter *params, const drcuml_instruction *testinst, drccodeptr codestart, drccodeptr codeend, UINT8 flagmask)
{
	const drcuml_opcode_info *opinfo = opcode_info_table[test->opcode];
	UINT8 ireg[DRCUML_REG_I_END - DRCUML_REG_I0] = { 0 };
	UINT8 freg[DRCUML_REG_F_END - DRCUML_REG_F0] = { 0 };
	char errorbuf[1024];
	char *errend = errorbuf;
	int pnum, regnum;

	*errend = 0;

	/* check flags */
	if (flags != (test->flags & flagmask))
	{
		errend += sprintf(errend, "  Flags ... result:%c%c%c%c%c  expected:%c%c%c%c%c\n",
			(flagmask & DRCUML_FLAG_U) ? ((flags & DRCUML_FLAG_U) ? 'U' : '.') : '-',
			(flagmask & DRCUML_FLAG_S) ? ((flags & DRCUML_FLAG_S) ? 'S' : '.') : '-',
			(flagmask & DRCUML_FLAG_Z) ? ((flags & DRCUML_FLAG_Z) ? 'Z' : '.') : '-',
			(flagmask & DRCUML_FLAG_V) ? ((flags & DRCUML_FLAG_V) ? 'V' : '.') : '-',
			(flagmask & DRCUML_FLAG_C) ? ((flags & DRCUML_FLAG_C) ? 'C' : '.') : '-',
			(flagmask & DRCUML_FLAG_U) ? ((test->flags & DRCUML_FLAG_U) ? 'U' : '.') : '-',
			(flagmask & DRCUML_FLAG_S) ? ((test->flags & DRCUML_FLAG_S) ? 'S' : '.') : '-',
			(flagmask & DRCUML_FLAG_Z) ? ((test->flags & DRCUML_FLAG_Z) ? 'Z' : '.') : '-',
			(flagmask & DRCUML_FLAG_V) ? ((test->flags & DRCUML_FLAG_V) ? 'V' : '.') : '-',
			(flagmask & DRCUML_FLAG_C) ? ((test->flags & DRCUML_FLAG_C) ? 'C' : '.') : '-');
	}

	/* check destination parameters */
	for (pnum = 0; pnum < ARRAY_LENGTH(test->param); pnum++)
		if (opinfo->param[pnum].output & PIO_OUT)
		{
			int psize = effective_test_psize(opinfo, pnum, test->size, test->param);
			UINT64 mask = U64(0xffffffffffffffff) >> (64 - 8 * psize);
			UINT64 result = 0;

			/* fetch the result from the parameters */
			switch (params[pnum].type)
			{
				/* integer registers fetch from the state */
				case DRCUML_PTYPE_INT_REGISTER:
					ireg[params[pnum].value - DRCUML_REG_I0] = 1;
					result = state->r[params[pnum].value - DRCUML_REG_I0].d;
					break;

				/* float registers fetch from the state */
				case DRCUML_PTYPE_FLOAT_REGISTER:
					freg[params[pnum].value - DRCUML_REG_I0] = 1;
					result = state->f[params[pnum].value - DRCUML_REG_F0].d;
					break;

				/* memory registers fetch from the memory address */
				case DRCUML_PTYPE_MEMORY:
					if (psize == 4)
						result = *(UINT32 *)(FPTR)params[pnum].value;
					else
						result = *(UINT64 *)(FPTR)params[pnum].value;
					break;

				default:
					break;
			}

			/* check against the mask */
			if (test->param[pnum] != UNDEFINED_U64 && (result & mask) != (test->param[pnum] & mask))
			{
				if ((UINT32)mask == mask)
					errend += sprintf(errend, "  Parameter %d ... result:%08X  expected:%08X\n", pnum,
										(UINT32)(result & mask), (UINT32)(test->param[pnum] & mask));
				else
					errend += sprintf(errend, "  Parameter %d ... result:%08X%08X  expected:%08X%08X\n", pnum,
										(UINT32)((result & mask) >> 32), (UINT32)(result & mask),
										(UINT32)((test->param[pnum] & mask) >> 32), (UINT32)(test->param[pnum] & mask));
			}
		}

	/* check source integer parameters for unexpected alterations */
	for (regnum = 0; regnum < ARRAY_LENGTH(state->r); regnum++)
		if (ireg[regnum] == 0 && istate->r[regnum].d != state->r[regnum].d)
			errend += sprintf(errend, "  Register i%d ... result:%08X%08X  originally:%08X%08X\n", regnum,
								(UINT32)(state->r[regnum].d >> 32), (UINT32)state->r[regnum].d,
								(UINT32)(istate->r[regnum].d >> 32), (UINT32)istate->r[regnum].d);

	/* check source float parameters for unexpected alterations */
	for (regnum = 0; regnum < ARRAY_LENGTH(state->f); regnum++)
		if (freg[regnum] == 0 && *(UINT64 *)&istate->f[regnum].d != *(UINT64 *)&state->f[regnum].d)
			errend += sprintf(errend, "  Register f%d ... result:%08X%08X  originally:%08X%08X\n", regnum,
								(UINT32)(*(UINT64 *)&state->f[regnum].d >> 32), (UINT32)*(UINT64 *)&state->f[regnum].d,
								(UINT32)(*(UINT64 *)&istate->f[regnum].d >> 32), (UINT32)*(UINT64 *)&istate->f[regnum].d);

	/* output the error if we have one */
	if (errend != errorbuf)
	{
		char disasm[256];

		/* disassemble the test instruction */
		drcuml_disasm(testinst, disasm, drcuml);

		/* output a description of what went wrong */
		printf("\n");
		printf("----------------------------------------------\n");
		printf("Backend validation error:\n");
		printf("   %s\n", disasm);
		printf("\n");
		printf("Errors:\n");
		printf("%s\n", errorbuf);
		fatalerror("Error during validation");
	}
	return errend != errorbuf;
}
