/*************************************************************************
 *                                                                       *
 * Print Lanai instructions.                                            *
 *                                                                       *
 * Copyright (c) 1994, 1995 by Myricom, Inc.                             *
 * All rights reserved.                                                  *
 *                                                                       *
 * This program is free software; you can redistribute it and/or modify  *
 * it under the terms of version 2 of the GNU General Public License     *
 * as published by the Free Software Foundation.  Myricom requests that  *
 * all modifications of this software be returned to Myricom, Inc. for   *
 * redistribution.  The name of Myricom, Inc. may not be used to endorse *
 * or promote products derived from this software without specific prior *
 * written permission.                                                   *
 *                                                                       *
 * Myricom, Inc. makes no representations about the suitability of this  *
 * software for any purpose.                                             *
 *                                                                       *
 * THIS FILE IS PROVIDED "AS-IS" WITHOUT WARRANTY OF ANY KIND, WHETHER   *
 * EXPRESSED OR IMPLIED, INCLUDING THE WARRANTY OF MERCHANTABILITY OR    *
 * FITNESS FOR A PARTICULAR PURPOSE.  MYRICOM, INC. SHALL HAVE NO        *
 * LIABILITY WITH RESPECT TO THE INFRINGEMENT OF COPYRIGHTS, TRADE       *
 * SECRETS OR ANY PATENTS BY THIS FILE OR ANY PART THEREOF.              *
 *                                                                       *
 * In no event will Myricom, Inc. be liable for any lost revenue         *
 * or profits or other special, indirect and consequential damages, even *
 * if Myricom has been advised of the possibility of such damages.       *
 *                                                                       *
 * Other copyrights might apply to parts of this software and are so     *
 * noted when applicable.                                                *
 *                                                                       *
 * Myricom, Inc.                    Email: info@myri.com                 *
 * 325 N. Santa Anita Ave.          World Wide Web: http://www.myri.com/ *
 * Arcadia, CA 91024                                                     *
 *************************************************************************/
/* initial version released 5/95 */
/* This file is based upon <> from the Gnu binutils-2.5.2
   release, which had the following copyright notice: */

/* Print SPARC instructions.
   Copyright 1989, 1991, 1992, 1993 Free Software Foundation, Inc.

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; either version 2 of the License, or
(at your option) any later version.

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, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

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

#include "ansidecl.h"
#include "opcode/lanai.h"
#include "disas-asm.h"

static char *reg_names[] = {
	"r0",
	"r1",
	"pc",
	"ps",
	"sp",
	"fp",
	"r6",
	"r7",
	"r8",
	"r9",
	"r10",
	"r11",
	"r12",
	"r13",
	"r14",
	"r15",
	"r16",
	"r17",
	"r18",
	"r19",
	"r20",
	"r21",
	"r22",
	"r23",
	"r24",
	"r25",
	"r26",
	"r27",
	"r28",
	"r29",
	"r30",
	"r31",
};

static char *op_names[] = { "add", "addc", "sub", "subb", "and", "or", "xor", "sh" };

/* Nonzero if INSN is the opcode for a delayed branch.  */
static int is_delayed_branch(unsigned long insn);

static int is_delayed_branch(unsigned long insn) {
	int i;

	for (i = 0; i < NUMOPCODES; i++) {
		CONST struct lanai_opcode *opcode = &lanai_opcodes[i];
		if ((opcode->match & insn) == opcode->match && (opcode->lose & insn) == 0) {
			return (opcode->flags & F_BR);
		}
	}
	return 0;
}

static int opcodes_sorted = 0;
/* extern void qsort (); */
static int compare_opcodes(char *a, char *b);

/* Print one instruction from MEMADDR on INFO->STREAM.

   We suffix the instruction with a comment that gives the absolute
   address involved, as well as its symbolic form, if the instruction
   is preceded by a findable `sethi' and it either adds an immediate
   displacement to that register, or it is an `add' or `or' instruction
   on that register.  */
extern int print_insn_lanai(bfd_vma memaddr, disassemble_info *info) {
	FILE *stream = info->stream;
	bfd_byte buffer[4];
	unsigned int insn;
	register int i;

	if (!opcodes_sorted) {
		qsort((char *)lanai_opcodes, NUMOPCODES,
			sizeof(lanai_opcodes[0]),
			(int (*)(const void *, const void *))compare_opcodes);
		opcodes_sorted = 1;
	}

	{
		int status =
			(*info->read_memory_func)(memaddr, buffer, sizeof(buffer), info);
		if (status != 0) {
			(*info->memory_error_func)(status, memaddr, info);
			return -1;
		}
	}

	insn = bfd_getb32(buffer);

	info->insn_info_valid = 1; /* We do return this info */
	info->insn_type = dis_nonbranch; /* Assume non branch insn */
	info->branch_delay_insns = 0; /* Assume no delay */
	info->target = 0; /* Assume no target known */

	for (i = 0; i < NUMOPCODES; i++) {
		CONST struct lanai_opcode *opcode = &lanai_opcodes[i];
		if ((opcode->match & insn) == opcode->match && (opcode->lose & insn) == 0) {
			/* Nonzero means that we have found an instruction which has
			   the effect of adding or or'ing the imm13 field to rs1.  */
			int imm_added_to_rs1 = 0;

			/* Do we have an `add' or `or' immediate instruction where rs1 is
			   the same as rd?  */

			if (((!(opcode->match & 0x80000000) /* RI insn */
				     && (!(opcode->match & 0x70000000) /* RI add */
						|| (opcode->match & 0x70000000) == 0x50000000 /* RI or */
						)) ||
				    ((opcode->match & 0xf0000000) == 0xc0000000 /* RR insn */
					    && (!(opcode->match & 0x00000700) /* RR add */
						       || (opcode->match & 0x00000700) == 0x00000500 /* RR or */))) &&
				X_RS1(insn) == X_RD(insn)) {
				imm_added_to_rs1 = 1;
			}

#ifdef BAD
			if (X_RS1(insn) != X_RD(insn) && strchr(opcode->args, 'r') != 0)
				/* Can't do simple format if source and dest are different.  */
				continue;
#endif

			(*info->fprintf_func)(stream, "%s", opcode->name);

			{
				register CONST char *s;
				unsigned int imm;

				for (s = opcode->args; *s != '\0'; ++s) {

					(*info->fprintf_func)(stream, " ");

					switch (*s) {
					/* By default, just print the character. */
					default:
						(*info->fprintf_func)(stream, "%c", *s);
						break;

#define reg(n) (*info->fprintf_func)(stream, "%s", reg_names[n])
						// #define	reg(n)	(*info->fprintf_func) (stream, "%%%s", reg_names[n])
					case '1':
						reg(X_RS1(insn));
						break;

					case '2':
						reg(X_RS2(insn));
						break;

					case '3':
						reg(X_RS3(insn));
						break;

					case 'd':
						reg(X_RD(insn));
						break;

#undef reg

					case '4': /* Op1 (for RRR) */
						(*info->fprintf_func)(stream, "%s", op_names[X_OP1(insn)]);
						break;
					case '5': /* Op2 (for RRR) */
						(*info->fprintf_func)(stream, "%s", op_names[X_OP2(insn)]);
						if (insn & L3_RRR_F) {
							(*info->fprintf_func)(stream, ".f");
						}
						break;
					case '6': /* Op2 (for RRM) */
						(*info->fprintf_func)(stream, "%s", op_names[X_OP2(insn)]);
						break;

					case 'J':
						imm = X_C16(insn) << 16;
						goto print_immediate;
					case 'j':
						imm = X_C16(insn);
						goto print_immediate;
					case 'L':
						imm = (X_C16(insn) << 16) | 0xffff;
						goto print_immediate;
					case 'l':
						imm = X_C16(insn) | 0xffff0000;
						goto print_immediate;
					case 'k':
						/* This should never happen */
						(*info->fprintf_func)(stream, "***ERROR***");
						break;
					case 'o':
						imm = SIGN_EXT(X_C16(insn), 16);
						if (X_RS1(insn) == 0) {
							goto print_address;
						}
						goto print_immediate;
					case 's':
						imm = SIGN_EXT(X_C16(insn), 16);
						goto print_immediate;
					case 'i':
						imm = SIGN_EXT(X_C10(insn), 10);
						if (X_RS1(insn) == 0) {
							goto print_address;
						}
						goto print_immediate;
					case 'I':
						imm = X_C21(insn);
						goto print_address;
					case 'Y':
						imm = X_C21(insn);
						goto print_address;
					case 'B':
						imm = X_C25(insn);
						goto print_address;
					case 'b':
						imm = SIGN_EXT(X_C25(insn), 25);
						goto print_address;

					print_immediate:
						(*info->fprintf_func)(stream, "0x%x", imm);
						break;
					print_address:
						info->target = imm;
						(*info->print_address_func)(imm, info);
						break;

						/* Named registers */

					case 'P':
						(*info->fprintf_func)(stream, "%%pc");
						break;

					case 'p':
						(*info->fprintf_func)(stream, "%%ps");
						break;

					case 'Q':
						(*info->fprintf_func)(stream, "%%apc");
						break;

					case 'q':
						(*info->fprintf_func)(stream, "%%aps");
						break;

					case 'S':
						(*info->fprintf_func)(stream, "%%isr");
						break;

					case 'M':
						(*info->fprintf_func)(stream, "%%imr");
						break;

					case '!':
						(*info->fprintf_func)(stream, "%%r1");
						break;

					case '0':
						(*info->fprintf_func)(stream, "%%r0");
						break;
					}
				}
			}

			/* If we are adding or or'ing something to rs1, then
			   check to see whether the previous instruction was
			   a mov to the same register as in the or.
			   If so, attempt to print the result of the add or
			   or (in this context add and or do the same thing)
			   and its symbolic value.  */
			if (imm_added_to_rs1) {
				unsigned long prev_insn;
				int errcode;

				errcode =
					(*info->read_memory_func)(memaddr - 4, buffer, sizeof(buffer), info);
				prev_insn = bfd_getb32(buffer);

				if (errcode == 0) {
					/* If it is a delayed branch, we need to look at the
					   instruction before the delayed branch.  This handles
					   sequences such as

					   mov %hi(_foo), %r4
					   call _printf
					   or %r4, %lo(_foo), %r4
					   */

					if (is_delayed_branch(prev_insn)) {
						errcode = (*info->read_memory_func)(memaddr - 8, buffer, sizeof(buffer), info);
						prev_insn = bfd_getb32(buffer);
					}
				}

				/* If there was a problem reading memory, then assume
				   the previous instruction was not sethi.  */
				if (errcode == 0) {
					/* Is it an "{and,or} %r0,0x????????,%rd" to the same reg  */
					if (((prev_insn & 0xf07c0000) == 0x00000000 || (prev_insn & 0xf07c0000) == 0x50000000) && X_RD(prev_insn) == X_RS1(insn) && X_RD(prev_insn)) {
						(*info->fprintf_func)(stream, "\t! ");
						info->target = X_C16(insn) << (L3_RI_H & insn ? 16 : 0);
						if ((prev_insn & 0xf07c0000) == 0x50000000) {
							info->target |= X_C16(prev_insn) << (L3_RI_H & prev_insn ? 16 : 0);
						} else {
							info->target += X_C16(prev_insn) << (L3_RI_H & prev_insn ? 16 : 0);
						}
						(*info->print_address_func)(info->target, info);
						info->insn_type = dis_dref;
						info->data_size = 4; /* FIXME!!! */
					}
				}
			}

			info->data_size = F_DATA_SIZE(opcode->flags);

			if (opcode->flags & (F_UNBR | F_CONDBR | F_JSR)) {
				/* FIXME -- check is_annulled flag */
				if (opcode->flags & F_UNBR) {
					info->insn_type = dis_branch;
				} else if (opcode->flags & F_CONDBR) {
					info->insn_type = dis_condbranch;
				} else if (opcode->flags & F_JSR) {
					info->insn_type = dis_jsr;
				} else if (opcode->flags & F_BR) {
					info->branch_delay_insns = 1;
				}
			}

			return sizeof(buffer);
		}
	}

	info->insn_type = dis_noninsn; /* Mark as non-valid instruction */
	(*info->fprintf_func)(stream, "%#8x", insn);
	return sizeof(buffer);
}

/* Compare opcodes A and B.  */
static int compare_opcodes(char *a, char *b) {
	struct lanai_opcode *op0 = (struct lanai_opcode *)a;
	struct lanai_opcode *op1 = (struct lanai_opcode *)b;
	unsigned long int match0 = op0->match, match1 = op1->match;
	unsigned long int lose0 = op0->lose, lose1 = op1->lose;
	register unsigned int i;

	/* If a bit is set in both match and lose, there is something
	   wrong with the opcode table.  */
	if (match0 & lose0) {
		fprintf(stderr, "Internal error:  bad lanai-opcode.h: \"%s\", %#.8lx, %#.8lx\n",
			op0->name, match0, lose0);
		op0->lose &= ~op0->match;
		lose0 = op0->lose;
	}

	if (match1 & lose1) {
		fprintf(stderr, "Internal error: bad lanai-opcode.h: \"%s\", %#.8lx, %#.8lx\n",
			op1->name, match1, lose1);
		op1->lose &= ~op1->match;
		lose1 = op1->lose;
	}

	/* Because the bits that are variable in one opcode are constant in
	   another, it is important to order the opcodes in the right order.  */
	for (i = 0; i < 32; ++i) {
		unsigned long int x = 1 << i;
		int x0 = (match0 & x) != 0;
		int x1 = (match1 & x) != 0;

		if (x0 != x1) {
			return x1 - x0;
		}
	}

	for (i = 0; i < 32; ++i) {
		unsigned long int x = 1 << i;
		int x0 = (lose0 & x) != 0;
		int x1 = (lose1 & x) != 0;

		if (x0 != x1) {
			return x1 - x0;
		}
	}

	/* They are functionally equal.  So as long as the opcode table is
	   valid, we can put whichever one first we want, on aesthetic grounds.  */

	/* Our first aesthetic ground is that aliases defer to real insns.  */
	{
		int alias_diff = (op0->flags & F_ALIAS) - (op1->flags & F_ALIAS);
		if (alias_diff != 0) {
			/* Put the one that isn't an alias first.  */
			return alias_diff;
		}
	}

	/* Except for aliases, two "identical" instructions had
	   better have the same opcode.  This is a sanity check on the table.  */
	i = strcmp(op0->name, op1->name);
	if (i) {
		if (op0->flags & F_ALIAS) /* If they're both aliases, be arbitrary. */
		{
			return i;
		} else {
			fprintf(stderr,
				"Internal error: bad lanai-opcode.h: \"%s\" == \"%s\"\n",
				op0->name, op1->name);
		}
	}

	/* Fewer arguments are preferred.  */
	{
		int length_diff = strlen(op0->args) - strlen(op1->args);
		if (length_diff != 0) {
			/* Put the one with fewer arguments first.  */
			return length_diff;
		}
	}

	/* Put 1+i before i+1.  */
	{
		char *p0 = (char *)strchr(op0->args, '+');
		char *p1 = (char *)strchr(op1->args, '+');

		if (p0 && p1) {
			/* There is a plus in both operands.  Note that a plus
			   sign cannot be the first character in args,
			   so the following [-1]'s are valid.  */
			if (p0[-1] == 'i' && p1[1] == 'i') {
				/* op0 is i+1 and op1 is 1+i, so op1 goes first.  */
				return 1;
			}
			if (p0[1] == 'i' && p1[-1] == 'i') {
				/* op0 is 1+i and op1 is i+1, so op0 goes first.  */
				return -1;
			}
		}
	}

	/* They are, as far as we can tell, identical.
	   Since qsort may have rearranged the table partially, there is
	   no way to tell which one was first in the opcode table as
	   written, so just say there are equal.  */
	return 0;
}
