/* $Id: yasep.c,v 1.7 2011-11-29 04:31:14 khorben Exp $ */
/* version yg:20111203
   version yg:20120920 : CRC->LUT8 */

/!\  NOT TESTED /!\

/* Copyright (c) 2011 Pierre Pronchery <khorben@defora.org>
   This file is part of DeforaOS Devel asm and includes
   translations of parts of the YASEP's javascript assembler and disassembler

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, version 3 of the License.

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

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <stddef.h>
#include "Asm.h"
#include "yasep.h"

/* yasep */
/* private */
/* variables */
/* plug-in */
#define REG(name, size, id) { "" # name, size, id },
static ArchRegister _yasep_registers[] =
{
#include "yasep.reg"
	{ NULL,		0, 0 }
};
#undef REG

static ArchInstruction _yasep_instructions[] =
{
#include "yasep.ins"
#include "common.ins"
#include "null.ins"
};

#include "yasep.flags"

/* prototypes */
/* plug-in */
static int _yasep_encode(ArchPlugin * plugin, ArchInstruction * instruction,
		ArchInstructionCall * call);
static int _yasep_decode(ArchPlugin * plugin, ArchInstructionCall * call);


/* protected */
/* variables */
ArchPlugin arch_plugin =
{
	NULL,
	"yasep",
	NULL,
	_yasep_registers,
	_yasep_instructions,
	NULL,
	NULL,
	_yasep_encode,
	_yasep_decode
};


/* the "->opX" makes it difficult to make a single flexible macro */
#define GET_REG1() \
	name = call->operands[0].value._register.name;\
	size = AO_GET_SIZE(instruction->op1);\
	if((ar = helper->get_register_by_name_size(helper->arch, name, size)) == NULL)\
		return -1;

#define GET_REG2() \
	name = call->operands[1].value._register.name;\
	size = AO_GET_SIZE(instruction->op2);\
	if((ar = helper->get_register_by_name_size(helper->arch, name, size)) == NULL)\
		return -1;

#define GET_REG3() \
	name = call->operands[2].value._register.name;\
	size = AO_GET_SIZE(instruction->op3);\
	if((ar = helper->get_register_by_name_size(helper->arch, name, size)) == NULL)\
		return -1;

/* private */
/* functions */
/* plug-in */
/* yasep_encode */

static int _yasep_encode(ArchPlugin * plugin,
                         ArchInstruction * instruction,
      	                 ArchInstructionCall * call)
{
	/* separate the real opcode from the flags and form */
	uint32_t opcode = instruction->opcode & 255;
	uint32_t flags  = _yasep_flags[opcode >> 2];
	uint32_t form   = 1 << (instruction->flags & 31); /* the flag stores the log2(form) */

	int r1, r2, r3;
	ArchPluginHelper * helper = plugin->helper;
	uint16_t u16;

	ArchRegister * ar;
	size_t size;
	char const * name;

	/* note : the opcode already contains the correct LSB (the size (long/short)
            and Imm/reg flags may be already set in the .ins) however
	    FIELD_EXTIMM4 must be restored in this code (depending on flags)  */

	switch(form)
	{
/**/		case FORM_ALONE: /* NOP, HALT, INV */
			/* nothing */
			break;


/**/		case FORM_R:	/* only used by :
				   NEG = 0 - snd
				   NOT = 0 | ~snd */
			return -1; /*  FORM_R is diverted to FORM_iR */


/**/		case FORM_RR:
			/* used by ROP2, ASU, SHL...
			   ADD AND ANDN CALL CMPS CMPU ESB ESH EZB EZH GET IB IH IHH IN IPC IPE IPR
			   LUT8 MOV MUL8H MUL8L MULI NAND NEG OR ORN OUT PUT ROL ROR
			   SAR SHL SHR SMAX SMIN SUB UMAX UMIN XOR XORN */

			GET_REG1()
			r1 = ar->id;
			GET_REG2()
			r2 = ar->id; /* get the 2nd argument */
			opcode |= (r1 << OFFSET_SI4) | (r2 << OFFSET_SND);
                        if (opcode & 1) /* extended version ? clone the src address to dst3 */
                                opcode |= (r2 << OFFSET_DST3);
                        break;


/**/		case FORM_i: /* CRIT, only valid as a short instruction */
			opcode |= call->operands[0].value.immediate.value << OFFSET_SI4;
			break;


/**/		case FORM_iR:
			/* used by ROP2, ASU, SHL...  	and aliased from NOT & NEG
			ADD AND ANDN CALL CMPS CMPU GET IB IH IHH LUT8 MOV MUL8H MUL8L
			MULI NAND NEG NOR NOT OR ORN ROL ROR SAR SHL SHR SMAX SMIN SUB UMAX UMIN XOR XORN */

			if (flags & ALIAS_RR) { /* is it NEG or NOT ? */
				GET_REG1()
	                        opcode |= (ar->id << OFFSET_SND)
				break;
			}

			GET_REG2()
			opcode |= (ar->id << OFFSET_SND)
			       | (call->operands[0].value.immediate.value << OFFSET_SI4);
                        break;


/**/		case FORM_Ri:	/* IN IPC IPE IPR OUT PUT , no need to deal with dst3 */
			if (opcode & 1)		/* FIXME : Ri mostly system-related so non abortable and non extendable ? */
				return -1;
			GET_REG1()
			opcode |= (ar->id << OFFSET_SND)
			       |  (call->operands[1].value.immediate.value << OFFSET_SI4);
			break;


/**/		case FORM_IRR:	/* Classic "long immediate"
			ADD AND ANDN CALL2 LUT8 MUL8H MUL8L NAND NOR OR ORN
			ROL ROR SAR SHL SHR SMAX SMIN SUB UMAX UMIN XOR XORN */

			GET_REG2()
			r1 = ar->id;
			GET_REG3()
			r2 = ar->id;
			opcode |= 1 | (r1 << OFFSET_SND) | (r2 << OFFSET_SI4)
			       |  (call->operands[0].value.immediate.value << 16);
                        break;


/**/		case FORM_IR:	/* SB, and alias for some FORM_IRR
			ADD AND ANDN CALL CMPS CMPU GET IB IH IHH LUT8 MOV MUL8H MUL8L MULI
			NAND NOR OR ORN ROL ROR SAR SHL SHR SMAX SMIN SUB UMAX UMIN XOR XORN */

			opcode |= 1 | (call->operands[0].value.immediate.value << 16);
			if (flags & FLAG_IMM20)
				opcode |= ((call->operands[0].value.immediate.value >> 16) & 15) << OFFSET_SND);

			GET_REG2()
			r1 = ar->id;

			if (flags & SWAP_IR) { /* GET, MOV */
				opcode |= (r1 << OFFSET_SI4);
				break;
			}

			if (flags & ALIAS_IRR)  /* ADD... */
				opcode |= (r1 << OFFSET_SI4);

			opcode |= (r1 << OFFSET_SND);
                        break;


/**/		case FORM_RI:	/* IN IPC IPE IPR OUT PUT */

			GET_REG2()
			opcode |= 1 | (ar->id << OFFSET_SND)
			       |  (call->operands[0].value.immediate.value << 16);
                        break;


/**/		case FORM_iRR:	/* Extended instruction  NEG & NOT in FORM_RR  are aliased to this
				ADD AND ANDN CALL2 IB IH IHH LUT8 MUL8H MUL8L NAND NOR OR
				ORN ROL ROR SAR SHL SHR SMAX SMIN SUB UMAX UMIN XOR XORN */

			if (flags & ALIAS_RR) {
				/* only used by :
				   NEG = 0 - snd
				   NOT = 0 | ~snd */
				GET_REG2()
				r1 = ar->id;
				GET_REG3()
				r2 = ar->id;
				opcode |= FIELD_EXTIMM4 /* enable the cleared IMM4 field */
				       | (r1 << OFFSET_SND) | (r2 << OFFSET_DST3);
				break;
			}

			GET_REG2()
			r1 = ar->id;
			GET_REG3()
			r2 = ar->id;

			opcode |= FIELD_EXTIMM4 | (r1 << OFFSET_SND) | (r2 << OFFSET_DST3)
			       |  (call->operands[0].value.immediate.value << OFFSET_SI4);
			break;


/**/		case FORM_RRR:	/* Extended instruction
				ADD AND ANDN CALL2 IB IH IHH LUT8 MUL8H MUL8L
				NAND NOR OR ORN ROL ROR SAR SHL
				SHR SMAX SMIN SUB UMAX UMIN XOR XORN */
			GET_REG2()
			r1 = ar->id;
			GET_REG3()
			r2 = ar->id;
			GET_REG3()
			r2 = ar->id;

			opcode |= (r1 << OFFSET_SI4)
			       |  (r2 << OFFSET_SND)
			       |  (r3 << OFFSET_DST3)
			break;


		default: /* error */
			return -1;
	}


        if (opcode & 1)
	{	/* Long or extended instruction */
		opcode = _htol32(opcode);
		if(helper->write(helper->arch, &opcode, sizeof(opcode))!= sizeof(opcode))
			return -1;
	}
        else
	{	/* Short instruction */
		u16 = _htol16(opcode);
		if(helper->write(helper->arch, &u16, sizeof(u16)) != sizeof(u16))
			return -1;
	}

	return 0;
}


/* yasep_decode */
/!\ not done yet ! /!\

static int _yasep_decode(ArchPlugin * plugin, ArchInstructionCall * call)
{
	ArchPluginHelper * helper = plugin->helper;
	uint16_t u16, opcode;
	uint32_t instruction;
	ArchInstruction * ai;
	ArchRegister * ar;

	/* Instructions are at least 16 bits long, load the first chunk */
	if(helper->read(helper->arch, &u16, sizeof(u16)) != sizeof(u16))
		return -1;
	instruction = _htol16(u16);

	if(instruction & 0x1) /* Long instruction : fetch the following half-word */
	{
	        if(helper->read(helper->arch, &u16, sizeof(u16)) != sizeof(u16))
	                return -1;
        	instruction |= _htol16(u16) << 16;
	}

	/* Là je sais plus. */
	opcode = instruction & 0x00ff;
	if((ai = helper->get_instruction_by_opcode(helper->arch, 16, opcode))== NULL)
		return -1;
	call->name = ai->name;
	if((opcode & 0x3) == 0x0) /* RR */
	{
		call->operands[0].definition = ai->op1;
		if((ar = helper->get_register_by_id_size(helper->arch,
						((u16 & 0xf000) >> 12) & 0xf,
						AO_GET_SIZE(ai->op1))) == NULL)
			return -1;
		call->operands[0].value._register.name = ar->name;
		call->operands[1].definition = ai->op2;
		if((ar = helper->get_register_by_id_size(helper->arch,
						((u16 & 0x0f00) >> 8) & 0xf,
						AO_GET_SIZE(ai->op2))) == NULL)
			return -1;
		call->operands[1].value._register.name = ar->name;
		call->operands_cnt = 2;
	}
	else if((opcode & 0x03) == 0x2) /* IR */
	{
		call->operands[0].definition = ai->op1;
		call->operands[0].value.immediate.value = (u16 & 0xf000) >> 12;
		call->operands[1].definition = ai->op2;
		if((ar = helper->get_register_by_id_size(helper->arch,
						((u16 & 0x0f00) >> 8) & 0xf,
						AO_GET_SIZE(ai->op2))) == NULL)
			return -1;
		call->operands[1].value._register.name = ar->name;
		call->operands_cnt = 2;
	}
	return 0;
}
