#include <stdio.h>
#include <string.h>

#include "asm_sparc.h"

/*
	Auteur: Benoit Papillault
	Creation: 11/1997

	14/01/1998: On va gerer le delay slot de la maniere suivante
	a l'interieur du code de l'instruction ayant un delay slot (call,
	jmpl,branch), on appellera a nouveau
	desassembler_instruction_sparc_delay() mais avec le parametre
	in_delay_slot valant 1. Ceci nous permet d'appeler cette fonction
	en plein milieu de l'interpretation d'une autre.

	Exemple: call i1+i5; add i2+i3,i4 sera anlyse comme suit:

	call -> tmp = i1+i5
		add -> i4=i2+i3
		call tmp

	L'expression resultante est donc un judicieux regroupement des deux
	expressions coresondants a call et add.

	14/02/1998: amelioration de la traduction du jumpl. Reste encore
	a calculer l'addition des deux registres.

*/

struct desas asm_sparc = { "sparc", 32, 0,
	desassembler_instruction_sparc };

/* la structure des instructions sparc */
union sparc_insn
{
	dword	code;
};

/* acces de maniere portable au divers champs */

#define	disp30(op)	((op)->code & 0x3fffffff)
#define	branch_a(op)	(((op)->code & 0x20000000) >> 29)
#define	branch_cond(op)	(((op)->code & 0x1e000000) >> 25)
#define	disp22(op)	((op)->code & 0x003fffff)
#define	op2(op)		(((op)->code & 0x01c00000) >> 22)
#define	imm22(op)	disp22(op)
#define	rd(op)		((op->code >> 25) & 0x1f)
#define	imm13(op)	((op)->code & 0x1fff)
#define	op3(op)		(((op)->code >> 19) & 0x3f)
#define	ldst_i(op)	(((op)->code >> 13) & 0x1)
#define	rs1(op)		(((op)->code >> 14) & 0x1f)
#define	rs2(op)		((op)->code & 0x1f)
#define	op(x)		(((x)->code >> 30) & 0x3)

/* les indices des registres */

#define	REG_G0	0
#define	REG_O0	8
#define	REG_O7	15
#define	REG_L0	16
#define	REG_I0	24
#define	REG_I7	31

/* numero de registre special qui represente le code-condition courant */
#define	REG_CC	32

/* la table des registres */
static const char *reg_names[] =
{ "g0", "g1", "g2", "g3", "g4", "g5", "g6", "g7",	
  "o0", "o1", "o2", "o3", "o4", "o5", "sp", "o7",	
  "l0", "l1", "l2", "l3", "l4", "l5", "l6", "l7",	
  "i0", "i1", "i2", "i3", "i4", "i5", "fp", "i7"
};

/* la table des conditions */
static const char *cond_names[] = 
{ "n",  "e",  "le",  "l",
  "leu","cs", "neg", "vs",
  "a",  "ne", "g",   "ge",
  "gu", "cc", "pos", "vc"
};

int cond_conversion[] = {
	0,	EGAL,	INFERIEUR_OU_EGAL,	INFERIEUR,
	0,	0,	0,	0,
	0,	DIFFERENT,	SUPERIEUR,	SUPERIEUR_OU_EGAL,
	0,	0,	0,	0
};

#define	BRANCH_ALWAYS	8
#define	BRANCH_NEVER	0

int sparc_format1(union sparc_insn *insn,const byte *buffer,int buflen,
	int addr,char *result,struct instruction *p,int in_delay_slot)
{		
	int	dst_addr;
	struct quadruplet q;
	int	count=4;
	char	buf[100];

	dst_addr = addr+(int)(disp30(insn) << 2);
	sprintf(result,"call\t0x%x",dst_addr);

/*	p->est_call = 1;
	p->call_addr = dst_addr;
*/

	if (in_delay_slot)
		printf("Warning: already in delay slot\n");
	else
	{
		count += desassembler_instruction_sparc_delay(buffer,buflen,
				addr+4,buf,p,1);
		strcat(result,",");
		strcat(result,buf);
	}

	q.op = OP_CALL;
	q.resultat = operande_constante(dst_addr);
	expression_ajouter(&p->rep,&q);

	return count;
}

int sparc_conditions(union sparc_insn *insn,const byte *buffer,int buflen,
	char *cond,int addr,char *result,struct instruction *p,
	int in_delay_slot)
{
	/* cond vaut "b", "fbf" , "cb" */
	char *	abit = "";
	int	disp;
	struct quadruplet q;
	int	count = 4;
	char	buf[100];

	if (branch_a(insn)) abit = ",a";

	disp = ((int)disp22(insn) << 10 ) >>8;

	sprintf(result,"%s%s%s\t0x%x",cond,cond_names[branch_cond(insn)],abit,
		addr+disp);

	if (in_delay_slot)
		printf("Warning: already in delay slot\n");
	else
	{
		count += desassembler_instruction_sparc_delay(buffer,buflen,
				addr+4,buf,p,1);
		strcat(result,",");
		strcat(result,buf);
	}

	if (branch_cond(insn) == BRANCH_ALWAYS)
	{
/*		p->est_jump = 1;
		p->jump_addr = addr+disp;
*/
		q.op = OP_JUMP;
		q.resultat = operande_constante(addr+disp);
		expression_ajouter(&p->rep,&q);
	}
	else if (branch_cond(insn) != BRANCH_NEVER)
	{
/*		p->est_branch = 1;
		p->branch_addr = addr+disp;
*/

		q.op = OP_BRANCH;
		q.arg1.value = cond_conversion[branch_cond(insn)];
		q.arg2 = operande_registre(REG_CC);
		q.resultat = operande_constante(addr+disp);

		expression_ajouter(&p->rep,&q);
	}

	return count;
}

int sparc_format2(union sparc_insn *insn,const byte *buffer,int buflen,
	int addr,char *result,struct instruction *p,int in_delay_slot)
{
	struct quadruplet q;
	int	count = 4;

	q.op = NOP;

	switch (op2(insn))
	{
	case 2: /* bicc */
		count=sparc_conditions(insn,buffer,buflen,"b",addr,result,p,
			in_delay_slot);
		break;
	case 4: /* sethi */
		sprintf(result,"sethi\t0x%lx,%%%s",
			imm22(insn)<<10,reg_names[rd(insn)]);
		if (imm22(insn) == 0 && rd(insn) == REG_G0)
		{
			q.op = OP_NOP;
		}
		else
		{
			q.op = OP_EGAL;
			q.arg1 = operande_constante(imm22(insn)<<10);
			q.resultat = operande_registre(rd(insn));
		}
		break;
	case 6: /* fbfcc */
		count=sparc_conditions(insn,buffer,buflen,"fbf",addr,result,p,
			in_delay_slot);
		break;
	case 7: /* cbccc */
		count=sparc_conditions(insn,buffer,buflen,"cbc",addr,result,p,
			in_delay_slot);
		break;
	default:
		strcpy(result,"unimp");
		break;
	}

	if (q.op != NOP)
		expression_ajouter(&p->rep,&q);

	return count;
}

int sparc_format3_2(union sparc_insn *insn,const byte *buffer,int buflen,
	int addr,char *result,struct instruction *p,int in_delay_slot)
{
	char	opcode[50];
	int	imm;
	int	rs1_val, rs2_val, rd_val;
	struct quadruplet q;
	char	buf[100];
	int	count = 4;
	int	plus_delay_slot = 0; /* =1 indique que l'on ajoute buf a la
					fin de result */

	rs1_val = rs1(insn);
	rs2_val = rs2(insn);
	rd_val = rd(insn);
	imm = ((int)imm13(insn) << 19) >> 19;

	q.op = NOP;
	q.arg1 = operande_registre(rs1_val);
	q.resultat = operande_registre(rd_val);

	if (ldst_i(insn))
	{
		imm = ((int)imm13(insn) << 19) >> 19;
		q.arg2 = operande_constante(imm);
	}
	else
	{
		q.arg2 = operande_registre(rs2_val);
	}
	switch (op3(insn))
	{
	case 0x0:
		strcpy(opcode,"add");
		q.op = OP_ADD;
		break;
	case 0x1:
		strcpy(opcode,"and");
		break;
	case 0x2:
		strcpy(opcode,"or");
		q.op = OP_OR;
		break;
	case 0x4:
		strcpy(opcode,"sub");
		q.op = OP_SUB;
		break;
	case 0x10:
		strcpy(opcode,"addcc");
		break;
	case 0x11:
		strcpy(opcode,"andcc");
		break;
	case 0x12:
		strcpy(opcode,"orcc");
		break;
	case 0x14:
		strcpy(opcode,"subcc"); /* se transforme en cmp dans le cas
						ou rd = %g0 */
		q.op = OP_SUB;
		expression_ajouter(&p->rep,&q);
		q.op = OP_CMP;
		q.resultat = operande_registre(REG_CC);
		break;
	case 0x25:
		strcpy(opcode,"sll");
		break;
	case 0x27:
		strcpy(opcode,"sra");
		break;
	case 0x38:

/* Dans un cas jmpl est une instruction peut avoir le meme effet
	qu'un return. Dans les autres cas, on a affaire a un jump vers une
	adresse non determinee. Dans tous les cas, il ne faut pas desassembler
	le code qui suit, on l'indique par p->est_ret = 1. Le cas du return
	est (ldst_i(insn) && imm == 8 && rs1(insn) == REG_I7). Mais ceci
	depend si l'on a effectue un save au prealable.

	save + jumpl %i7,8,%g0	=> ret
	jumpl %o7,8,%g0		=> ret

	Les autres cas sont:
	save + jumpl %o7,8,&g0	=> ???
	jumpl %i7,8,%g0		=> ret2

	Donc on va considere que jumpl %i7/%o7,8,%go = ret.

	De maniere generale, il s'agit d'un saut en rs1+rs2 avec sauvegarde
	de l'adresse de l'instruction courante dans rd. Et dans le cas d'un
	call, rd = %o7.

	Donc, un jumpl %o5,%g0,%o7 est un call indexe (appel d'une fonction
	grace a un pointeur de fonction en C).
*/

		strcpy(opcode,"jmpl");
		if (in_delay_slot)
			printf("Warning: already in delay slot\n");
		else
		{
			count += desassembler_instruction_sparc_delay(buffer,
					buflen,addr+4,buf,p,1);
			plus_delay_slot = 1;
		}
		if ( (rs1_val==REG_I7 || rs1_val==REG_O7) && imm == 8
			&& rd_val == REG_G0)
			q.op = OP_RET;
		else if (rd_val == REG_O7)
			q.op = OP_CALL;
		else	q.op = OP_JUMP;

/* Oubli: rd = rs1+rs2 , call/jump rd */

		break;
	case 0x3c:
		strcpy(opcode,"save");
		break;
	case 0x3d:
		strcpy(opcode,"restore");
		break;
	default:
		sprintf(opcode,"sparc_format3_2 op3=0x%lx",op3(insn));
		break;
	}

	if (ldst_i(insn))
	{
		sprintf(result,"%s\t%%%s,%d,%%%s",opcode,
			reg_names[rs1_val],imm,
			reg_names[rd_val]);
	}
	else
	{
		sprintf(result,"%s\t%%%s,%%%s,%%%s",opcode,
			reg_names[rs1_val],reg_names[rs2_val],
			reg_names[rd_val]);
	}

	if (plus_delay_slot)
	{
		strcat(result,",");
		strcat(result,buf);
	}

	if (q.op != NOP)
		expression_ajouter(&p->rep,&q);

	return count;
}

int sparc_format3_3(union sparc_insn *insn,const byte *buffer,int buflen,
	int addr,char *result,struct instruction *p,int in_delay_slot)
{
	char	opcode[50];
	int	imm;
	int	rs1_val, rs2_val, rd_val;
	struct quadruplet q;
	int	count = 4;

	rs1_val = rs1(insn);
	rs2_val = rs2(insn);
	rd_val = rd(insn);
	imm = ((int)imm13(insn) << 19) >> 19;

	q.op = NOP;
	q.arg1 = operande_registre(rs1_val);
	q.resultat = operande_registre(rd_val);

	if (ldst_i(insn))
	{
		imm = ((int)imm13(insn) << 19) >> 19;
		q.arg2 = operande_constante(imm);
	}
	else
	{
		q.arg2 = operande_registre(rs2_val);
	}

	switch (op3(insn))
	{
	case 0x0:
		strcpy(opcode,"ld");
		q.op = OP_ADD;
		q.resultat = operande_temporaire();
		expression_ajouter(&p->rep,&q);
		q.op = OP_LOAD;
		q.arg1 = operande_constante(0);
		q.arg2 = q.resultat;
		q.resultat = operande_registre(rd_val);
		break;
	case 0x1:
		strcpy(opcode,"ldub");
		break;
	case 0x2:
		strcpy(opcode,"lduh");
		break;
	case 0x4:
		strcpy(opcode,"st");
		q.op = OP_ADD;
		q.resultat = operande_temporaire();
		expression_ajouter(&p->rep,&q);
		q.op = OP_STORE;
		q.arg1 = operande_constante(0);
		q.arg2 = q.resultat;
		q.resultat = operande_registre(rd_val);
		break;
	case 0x5:
		strcpy(opcode,"stb");
		break;
	case 0x9:
		strcpy(opcode,"ldsb");
		break;
	case 0xa:
		strcpy(opcode,"ldsh");
		break;
	default:
		sprintf(opcode,"sparc_format3_3 op3=0x%lx",op3(insn));
		break;
	}

	if (ldst_i(insn))
	{
		sprintf(result,"%s\t%%%s,%d,%%%s",opcode,
			reg_names[rs1_val],imm,
			reg_names[rd_val]);
	}
	else
	{
		sprintf(result,"%s\t%%%s,%%%s,%%%s",opcode,
			reg_names[rs1_val],reg_names[rs2_val],
			reg_names[rd_val]);
	}

	if (q.op != NOP)
		expression_ajouter(&p->rep,&q);

	return count;
}

int sparc_format3(union sparc_insn *insn,const byte *buffer,int buflen,
	int addr,char *result,struct instruction *p,int in_delay_slot)
{
	int	count = 4;

	switch (op(insn))
	{
	case 2:
		count=sparc_format3_2(insn,buffer,buflen,addr,result,p,
			in_delay_slot);
		break;
	case 3:
		count=sparc_format3_3(insn,buffer,buflen,addr,result,p,
			in_delay_slot);
		break;
	}

	return count;
}


/* desassemble une seule instruction et renvoie le resultat dans result.
	buffer de taille buflen contient l'instruction a desassembler.
	addr est l'adresse virtuelle courante de l'instruction.
	La valeur retournee est le nombre d'octets ayant servis a l'instruction */

int desassembler_instruction_sparc_delay(const byte *buffer,int buflen,int addr,
	char *buf,struct instruction *p,int in_delay_slot)
{
	union sparc_insn insn;
	int	count;

	insn.code = ((dword)buffer[0]<<24) | ((dword)buffer[1]<<16) |
			((dword)buffer[2]<<8) | ((dword)buffer[3]);

	switch (op(&insn))
	{
	case 0: /* bicc. fbcc.cbccc,sethi */
		count=sparc_format2(&insn,buffer+4,buflen,addr,buf,p,
			in_delay_slot);
		break;
	case 1: /* call */
		count=sparc_format1(&insn,buffer+4,buflen,addr,buf,p,
			in_delay_slot);
		break;
	case 2:
	case 3:
		count=sparc_format3(&insn,buffer+4,buflen,addr,buf,p,
			in_delay_slot);
		break;
	}

	return count;
}

int desassembler_instruction_sparc(const byte *buffer,int buflen,int addr,
	struct instruction *p)
{
	int	count;
	char	buf1[100];

	count = desassembler_instruction_sparc_delay(buffer,buflen,addr,buf1,p,
			0);

	p->line = strdup(buf1);

	return count;
}
