/* Print IBM RS/6000 instructions for GNU software.
   Copyright 1991 Free Software Foundation, Inc.
   Contributed by IBM Corporation.

This file is part of GDB and the GNU binutils.

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 "defs.h"
#include "opcode/rs6k.h"

/* Print the rs6k instruction at address MEMADDR in debugged memory,
   on STREAM.  Returns length of the instruction, in bytes.  */

int
print_insn (memaddr, stream)
  CORE_ADDR memaddr;
  FILE *stream;
{
	int  pop, eop, probable_eop;	/* primary and extended opcodes */
	int  min, max;
	int  best = -1;			/* found best opcode index	*/
	int  oldbest = -1;
	unsigned int the_insn;

	read_memory (memaddr, &the_insn, sizeof (the_insn));
	pop = (unsigned)(the_insn >> 26);
	min = 0, max = NOPCODES-1;

	while (min < max) {
	  best = (min + max) / 2;

	  /* see if we are running in loops */
	  if (best == oldbest)
	    goto not_found;
	  oldbest = best;

	  if (pop < rs6k_ops [best].p_opcode)
	    max = best;

	  else if (pop > rs6k_ops [best].p_opcode)
	    min = best;

	  else {
	    /* Opcode matched, check extended opcode. */

	    if (rs6k_ops [best].e_opcode == -1) {
	      /* there is no valid extended opcode, what we've got is
		 just fine. */
	      goto insn_found;
	    }

	    /* Largest possible value of extended opcode. */
	    probable_eop = ((the_insn) >> 1) & 0x3ff;

	    eop = probable_eop & eopMask [rs6k_ops [best].format];

	    if (eop < rs6k_ops [best].e_opcode) {

	      while (pop == rs6k_ops [best].p_opcode) {
		if (eop == rs6k_ops [best].e_opcode)	/* found it! */
		  goto insn_found;
	        --best;
	        eop = probable_eop & eopMask [rs6k_ops [best].format];
	      }
	      goto not_found;
	    }

	    else if (eop > rs6k_ops [best].e_opcode) {

	      while (pop == rs6k_ops [best].p_opcode) {
		if (eop == rs6k_ops [best].e_opcode)	/* found it! */
		  goto insn_found;
	        ++best;
	        eop = probable_eop & eopMask [rs6k_ops [best].format];
	      }
	      goto not_found;
	    }

	    else /*  eop == rs6k_ops [best].e_opcode */
	      goto insn_found;
	  }
	}	

	best = min;
	if (pop == rs6k_ops [best].p_opcode &&
           (rs6k_ops [best].e_opcode == -1 || rs6k_ops [best].e_opcode == eop))
	    goto insn_found;

	else
	  goto not_found;


insn_found:
	print_operator (stream, memaddr, the_insn, best);
	return 4;

not_found:
	fprintf (stream, "0x%08x", the_insn);
	return 4;
}



/* condition code names */
static char *cond_code [] = {
  "lt", "gt", "eq", "so", "ge", "le", "ne", "ns", "nl", "ng", "z", "nz" };


print_operator (stream, memaddr, insn_word, insn_no)
FILE	*stream;
long	memaddr;
long	insn_word;
int	insn_no;
{
  char buf [20];
  char *qq = buf;
  char *pp = rs6k_ops[insn_no].opr_ext;
  int tmp;
  int nocomma = 0;			/* true if no comma needed */

  if (pp) {
    while (*pp) {

      switch ( *pp ) {
	case '.':
	  if (insn_word & 0x1)
	    *qq++ = '.';
	  break;

	case 'l':
	  if (insn_word & 0x1)
	   *qq++ = 'l';
	  break;

	case 't':
	  if ((insn_word & 0x03e00000) == 0x01800000)
	   *qq++ = 't';
	  break;

	case 'f':
	  if ((insn_word & 0x03e00000) == 0x00800000)
	   *qq++ = 'f';
	  break;

	case 'a':
	  if (insn_word & 0x2)
	   *qq++ = 'a';
	  break;

	case 'o':
	  if (insn_word & 0x4000)
	   *qq++ = 'o';
	  break;

	case '1':		/* exception #1 for bb/bc ambiguity */
	  tmp = (insn_word >> 21) & 0x1f;	/* extract BO	*/
	  if (tmp != 0xc && tmp != 0x4) {
	    /* you can't use `bb' now. switch to `bc' */
	    *(qq-1) = 'c';
	    ++insn_no;
	    pp = rs6k_ops[insn_no].opr_ext;
	    continue;
	  }
	  break;

	default:
	  abort ();
      }
      ++pp;
    }
  }
  *qq = '\0';

  fprintf (stream, "%s%s\t", rs6k_ops[insn_no].operator, buf);

  /* parse the operand now. */
  pp = rs6k_ops[insn_no].oprnd_format;

  while (1) {
    switch (*pp) {
      case TO	:
	fprintf (stream, "%d", (insn_word >> 21) & 0x1f);
	break;

      case RT	:
      case RS	:
	fprintf (stream, "r%d", (insn_word >> 21) & 0x1f);
	break;

      case LI	:
	tmp  = (insn_word >> 16) & 0x1f;
	if (tmp > 11) {
	  fprintf (stream, "{unknown cond code: 0x%x}", insn_word);
	  tmp = 0;
	}
	fprintf (stream, "%s", cond_code [tmp]);
	break;

      case A2	:
      case TA14	:
	tmp = (insn_word & 0xfffc);
	if (tmp & 0x8000)		/* fix sign extension	*/
	  tmp -= 0x10000;

	if ((insn_word & 0x2) == 0)	/* if AA not set	*/
	  tmp += memaddr;

        print_address (tmp, stream);
	break;

      case TA24	:
	tmp = insn_word & 0x03fffffc;
	if (tmp & 0x2000000)
	  tmp -= 0x4000000;
	
	if ((insn_word & 0x2) == 0)		/* if no AA bit set */
	  tmp += memaddr;

        print_address (tmp, stream);
	break;

      case LEV	:			/* for svc only */
	if (insn_word & 0x2) {		/* SA is set	*/
	  nocomma = 1;
	}
	else
          fprintf (stream, "%d", (insn_word >> 5) & 0x7f);
	break;

      case FL1	:			/* for svc only */
	if (insn_word & 0x2) {		/* SA is set	*/
	  nocomma = 1;
	}
	else
          fprintf (stream, "%d", (insn_word >> 12) & 0xf);
	break;

      case FL2	:			/* for svc only	*/
	nocomma = 0;
	if (insn_word & 0x2)		/* SA is set	*/
	  fprintf (stream, "%d", (insn_word >> 2) & 0x3fff);
	else
          fprintf (stream, "%d", (insn_word >> 2) & 0x7);
	break;

      case RA	:
	if (nocomma) {
	  fprintf (stream, "r%d)", (insn_word >> 16) & 0x1f);
	  nocomma = 0;
	}
	else
	  fprintf (stream, "r%d", (insn_word >> 16) & 0x1f);
	break;

      case RB	:
	fprintf (stream, "r%d", (insn_word >> 11) & 0x1f);
	break;

      case SI	:
	tmp = insn_word & 0xffff;
	if (tmp & 0x8000)
	  tmp -= 0x10000;
	fprintf (stream, "%d", tmp);
	break;

      case UI	:
	fprintf (stream, "%d", insn_word & 0xffff);
	break;

      case BF	:
	fprintf (stream, "%d", (insn_word >> 23) & 0x7);
	break;

      case BFA	:
	fprintf (stream, "%d", (insn_word >> 18) & 0x7);
	break;

      case BT	:
	fprintf (stream, "%d", (insn_word >> 21) & 0x1f);
	break;

      case BA	:
	fprintf (stream, "%d", (insn_word >> 16) & 0x1f);
	break;

      case BB	:
	fprintf (stream, "%d", (insn_word >> 11) & 0x1f);
	break;

      case BO	:
	fprintf (stream, "%d", (insn_word >> 21) & 0x1f);
	break;

      case BI	:
	fprintf (stream, "%d", (insn_word >> 16) & 0x1f);
	break;

      case SH	:
	fprintf (stream, "%d", (insn_word >> 11) & 0x1f);
	break;

      case MB	:
	fprintf (stream, "0x%x", (insn_word >> 6) & 0x1f);
	break;

      case ME	:
	fprintf (stream, "0x%x", (insn_word >> 1) & 0x1f);
	break;

      case SPR	:
	fprintf (stream, "%d", (insn_word >> 16) & 0x1f);
	break;

      case DIS	:
	nocomma = 1;
	tmp = insn_word & 0xffff;
	if (tmp & 0x8000)
	  tmp -= 0x10000;
	fprintf (stream, "%d(", tmp);
	break;

      case FXM	:
	fprintf (stream, "0x%x", (insn_word >> 12) & 0xff);
	break;

      case FRT	:
      case FRS	:
	fprintf (stream, "f%d", (insn_word >> 21) & 0x1f);
	break;

      case FRA	:
	fprintf (stream, "f%d", (insn_word >> 16) & 0x1f);
	break;

      case FRB	:
	fprintf (stream, "f%d", (insn_word >> 11) & 0x1f);
	break;

      case FRC	:
	fprintf (stream, "f%d", (insn_word >> 6) & 0x1f);
	break;

      case FLM	:
	fprintf (stream, "0x%x", (insn_word >> 17) & 0xff);
	break;

      case NB	:
	fprintf (stream, "%d", (insn_word >> 11) & 0x1f);
	break;

      case I	:
	fprintf (stream, "%d", (insn_word >> 12) & 0xf);
	break;

      default	:
	fprintf (stream,
		 "{Internal error: Unknown operand format identifier %d}",
		 *pp);
    }
    ++pp;

    if (*pp == '\0')
      break;
    else if (!nocomma)
      fputc(',', stream);
  }
}