/* mmix-dis.c -- Disassemble MMIX instructions.
   Copyright (C) 2000-2025 Free Software Foundation, Inc.
   Written by Hans-Peter Nilsson (hp@bitrange.com)

   This file is part of the GNU opcodes library.

   This library 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 3, or (at your option)
   any later version.

   It 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 file; see the file COPYING.  If not, write to the Free
   Software Foundation, 51 Franklin Street - Fifth Floor, Boston,
   MA 02110-1301, USA.  */

#include "sysdep.h"
#include <stdio.h>
#include "opcode/mmix.h"
#include "disassemble.h"
#include "libiberty.h"
#include "bfd.h"
#include "opintl.h"

#define BAD_CASE(x)						\
  do								\
   {								\
     opcodes_error_handler (_("bad case %d (%s) in %s:%d"),	\
			    x, #x, __FILE__, __LINE__);		\
     abort ();							\
   }								\
 while (0)

#define FATAL_DEBUG						\
 do								\
   {								\
     opcodes_error_handler (_("internal: non-debugged code "	\
			      "(test-case missing): %s:%d"),	\
			    __FILE__, __LINE__);		\
     abort ();							\
   }								\
 while (0)

#define ROUND_MODE(n)					\
 ((n) == 1 ? "ROUND_OFF" : (n) == 2 ? "ROUND_UP" :	\
  (n) == 3 ? "ROUND_DOWN" : (n) == 4 ? "ROUND_NEAR" :	\
  _("(unknown)"))

#define INSN_IMMEDIATE_BIT (IMM_OFFSET_BIT << 24)
#define INSN_BACKWARD_OFFSET_BIT (1 << 24)

#define MAX_REG_NAME_LEN       256
#define MAX_SPEC_REG_NAME_LEN  32
struct mmix_dis_info
 {
   const char *reg_name[MAX_REG_NAME_LEN];
   const char *spec_reg_name[MAX_SPEC_REG_NAME_LEN];

   /* Waste a little memory so we don't have to allocate each separately.
      We could have an array with static contents for these, but on the
      other hand, we don't have to.  */
   char basic_reg_name[MAX_REG_NAME_LEN][sizeof ("$255")];
 };

/* Initialize a target-specific array in INFO.  */

static bool
initialize_mmix_dis_info (struct disassemble_info *info)
{
  struct mmix_dis_info *minfop = malloc (sizeof (struct mmix_dis_info));
  long i;

  if (minfop == NULL)
    return false;

  memset (minfop, 0, sizeof (*minfop));

  /* Initialize register names from register symbols.  If there's no
     register section, then there are no register symbols.  */
  if ((info->section != NULL && info->section->owner != NULL)
      || (info->symbols != NULL
	  && info->symbols[0] != NULL
	  && bfd_asymbol_bfd (info->symbols[0]) != NULL))
    {
      bfd *abfd = info->section && info->section->owner != NULL
	? info->section->owner
	: bfd_asymbol_bfd (info->symbols[0]);
      asection *reg_section = bfd_get_section_by_name (abfd, "*REG*");

      if (reg_section != NULL)
	{
	  /* The returned symcount *does* include the ending NULL.  */
	  long symsize = bfd_get_symtab_upper_bound (abfd);
	  asymbol **syms = malloc (symsize);
	  long nsyms;

	  if (syms == NULL)
	    {
	      FATAL_DEBUG;
	      free (minfop);
	      return false;
	    }
	  nsyms = bfd_canonicalize_symtab (abfd, syms);

	  /* We use the first name for a register.  If this is MMO, then
	     it's the name with the first sequence number, presumably the
	     first in the source.  */
	  for (i = 0; i < nsyms && syms[i] != NULL; i++)
	    {
	      if (syms[i]->section == reg_section
		  && syms[i]->value < MAX_REG_NAME_LEN
		  && minfop->reg_name[syms[i]->value] == NULL)
		minfop->reg_name[syms[i]->value] = syms[i]->name;
	    }
	  free (syms);
	}
    }

  /* Fill in the rest with the canonical names.  */
  for (i = 0; i < MAX_REG_NAME_LEN; i++)
    if (minfop->reg_name[i] == NULL)
      {
	sprintf (minfop->basic_reg_name[i], "$%ld", i);
	minfop->reg_name[i] = minfop->basic_reg_name[i];
      }

  /* We assume it's actually a one-to-one mapping of number-to-name.  */
  for (i = 0; mmix_spec_regs[i].name != NULL; i++)
    minfop->spec_reg_name[mmix_spec_regs[i].number] = mmix_spec_regs[i].name;

  info->private_data = (void *) minfop;
  return true;
}

/* A table indexed by the first byte is constructed as we disassemble each
   tetrabyte.  The contents is a pointer into mmix_insns reflecting the
   first found entry with matching match-bits and lose-bits.  Further
   entries are considered one after one until the operand constraints
   match or the match-bits and lose-bits do not match.  Normally a
   "further entry" will just show that there was no other match.  */

static const struct mmix_opcode *
get_opcode (unsigned long insn)
{
  static const struct mmix_opcode **opcodes = NULL;
  const struct mmix_opcode *opcodep = mmix_opcodes;
  unsigned int opcode_part = (insn >> 24) & 255;

  if (opcodes == NULL)
    opcodes = xcalloc (256, sizeof (struct mmix_opcode *));

  opcodep = opcodes[opcode_part];
  if (opcodep == NULL
      || (opcodep->match & insn) != opcodep->match
      || (opcodep->lose & insn) != 0)
    {
      /* Search through the table.  */
      for (opcodep = mmix_opcodes; opcodep->name != NULL; opcodep++)
	{
	  /* FIXME: Break out this into an initialization function.  */
	  if ((opcodep->match & (opcode_part << 24)) == opcode_part
	      && (opcodep->lose & (opcode_part << 24)) == 0)
	    opcodes[opcode_part] = opcodep;

	  if ((opcodep->match & insn) == opcodep->match
	      && (opcodep->lose & insn) == 0)
	    break;
	}
    }

  if (opcodep->name == NULL)
    return NULL;

  /* Check constraints.  If they don't match, loop through the next opcode
     entries.  */
  do
    {
      switch (opcodep->operands)
	{
	  /* These have no restraint on what can be in the lower three
	     bytes.  */
	case mmix_operands_regs:
	case mmix_operands_reg_yz:
	case mmix_operands_regs_z_opt:
	case mmix_operands_regs_z:
	case mmix_operands_jmp:
	case mmix_operands_pushgo:
	case mmix_operands_pop:
	case mmix_operands_sync:
	case mmix_operands_x_regs_z:
	case mmix_operands_neg:
	case mmix_operands_pushj:
	case mmix_operands_regaddr:
	case mmix_operands_get:
	case mmix_operands_set:
	case mmix_operands_save:
	case mmix_operands_unsave:
	case mmix_operands_xyz_opt:
	  return opcodep;

	  /* For a ROUND_MODE, the middle byte must be 0..4.  */
	case mmix_operands_roundregs_z:
	case mmix_operands_roundregs:
	  {
	    int midbyte = (insn >> 8) & 255;

	    if (midbyte <= 4)
	      return opcodep;
	  }
	break;

	case mmix_operands_put:
	  /* A "PUT".  If it is "immediate", then no restrictions,
	     otherwise we have to make sure the register number is < 32.  */
	  if ((insn & INSN_IMMEDIATE_BIT)
	      || ((insn >> 16) & 255) < 32)
	    return opcodep;
	  break;

	case mmix_operands_resume:
	  /* Middle bytes must be zero.  */
	  if ((insn & 0x00ffff00) == 0)
	    return opcodep;
	  break;

	default:
	  BAD_CASE (opcodep->operands);
	}

      opcodep++;
    }
  while ((opcodep->match & insn) == opcodep->match
	 && (opcodep->lose & insn) == 0);

  /* If we got here, we had no match.  */
  return NULL;
}

static inline const char *
get_reg_name (const struct mmix_dis_info * minfop, unsigned int x)
{
  if (x >= MAX_REG_NAME_LEN)
    return _("*illegal*");
  return minfop->reg_name[x];
}

static inline const char *
get_spec_reg_name (const struct mmix_dis_info * minfop, unsigned int x)
{
  if (x >= MAX_SPEC_REG_NAME_LEN)
    return _("*illegal*");
  return minfop->spec_reg_name[x];
}

/* The main disassembly function.  */

int
print_insn_mmix (bfd_vma memaddr, struct disassemble_info *info)
{
  unsigned char buffer[4];
  unsigned long insn;
  unsigned int x, y, z;
  const struct mmix_opcode *opcodep;
  int status = (*info->read_memory_func) (memaddr, buffer, 4, info);
  struct mmix_dis_info *minfop;

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

  /* FIXME: Is -1 suitable?  */
  if (info->private_data == NULL
      && ! initialize_mmix_dis_info (info))
    return -1;

  minfop = (struct mmix_dis_info *) info->private_data;
  x = buffer[1];
  y = buffer[2];
  z = buffer[3];

  insn = bfd_getb32 (buffer);

  opcodep = get_opcode (insn);

  if (opcodep == NULL)
    {
      (*info->fprintf_func) (info->stream, _("*unknown*"));
      return 4;
    }

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

  /* Present bytes in the order they are laid out in memory.  */
  info->display_endian = BFD_ENDIAN_BIG;

  info->insn_info_valid = 1;
  info->bytes_per_chunk = 4;
  info->branch_delay_insns = 0;
  info->target = 0;
  switch (opcodep->type)
    {
    case mmix_type_normal:
    case mmix_type_memaccess_block:
      info->insn_type = dis_nonbranch;
      break;

    case mmix_type_branch:
      info->insn_type = dis_branch;
      break;

    case mmix_type_condbranch:
      info->insn_type = dis_condbranch;
      break;

    case mmix_type_memaccess_octa:
      info->insn_type = dis_dref;
      info->data_size = 8;
      break;

    case mmix_type_memaccess_tetra:
      info->insn_type = dis_dref;
      info->data_size = 4;
      break;

    case mmix_type_memaccess_wyde:
      info->insn_type = dis_dref;
      info->data_size = 2;
      break;

    case mmix_type_memaccess_byte:
      info->insn_type = dis_dref;
      info->data_size = 1;
      break;

    case mmix_type_jsr:
      info->insn_type = dis_jsr;
      break;

    default:
      BAD_CASE(opcodep->type);
    }

  switch (opcodep->operands)
    {
    case mmix_operands_regs:
      /*  All registers: "$X,$Y,$Z".  */
      (*info->fprintf_func) (info->stream, "%s,%s,%s",
			     get_reg_name (minfop, x),
			     get_reg_name (minfop, y),
			     get_reg_name (minfop, z));
      break;

    case mmix_operands_reg_yz:
      /* Like SETH - "$X,YZ".  */
      (*info->fprintf_func) (info->stream, "%s,0x%x",
			     get_reg_name (minfop, x), y * 256 + z);
      break;

    case mmix_operands_regs_z_opt:
    case mmix_operands_regs_z:
    case mmix_operands_pushgo:
      /* The regular "$X,$Y,$Z|Z".  */
      if (insn & INSN_IMMEDIATE_BIT)
	(*info->fprintf_func) (info->stream, "%s,%s,%d",
			       get_reg_name (minfop, x),
			       get_reg_name (minfop, y), z);
      else
	(*info->fprintf_func) (info->stream, "%s,%s,%s",
			       get_reg_name (minfop, x),
			       get_reg_name (minfop, y),
			       get_reg_name (minfop, z));
      break;

    case mmix_operands_jmp:
      /* Address; only JMP.  */
      {
	bfd_signed_vma offset = (x * 65536 + y * 256 + z) * 4;

	if (insn & INSN_BACKWARD_OFFSET_BIT)
	  offset -= (256 * 65536) * 4;

	info->target = memaddr + offset;
	(*info->print_address_func) (memaddr + offset, info);
      }
      break;

    case mmix_operands_roundregs_z:
      /* Two registers, like FLOT, possibly with rounding: "$X,$Z|Z"
	 "$X,ROUND_MODE,$Z|Z".  */
      if (y != 0)
	{
	  if (insn & INSN_IMMEDIATE_BIT)
	    (*info->fprintf_func) (info->stream, "%s,%s,%d",
				   get_reg_name (minfop, x),
				   ROUND_MODE (y), z);
	  else
	    (*info->fprintf_func) (info->stream, "%s,%s,%s",
				   get_reg_name (minfop, x),
				   ROUND_MODE (y),
				   get_reg_name (minfop, z));
	}
      else
	{
	  if (insn & INSN_IMMEDIATE_BIT)
	    (*info->fprintf_func) (info->stream, "%s,%d",
				   get_reg_name (minfop, x), z);
	  else
	    (*info->fprintf_func) (info->stream, "%s,%s",
				   get_reg_name (minfop, x),
				   get_reg_name (minfop, z));
	}
      break;

    case mmix_operands_pop:
      /* Like POP - "X,YZ".  */
      (*info->fprintf_func) (info->stream, "%d,%d", x, y*256 + z);
      break;

    case mmix_operands_roundregs:
      /* Two registers, possibly with rounding: "$X,$Z" or
	 "$X,ROUND_MODE,$Z".  */
      if (y != 0)
	(*info->fprintf_func) (info->stream, "%s,%s,%s",
			       get_reg_name (minfop, x),
			       ROUND_MODE (y),
			       get_reg_name (minfop, z));
      else
	(*info->fprintf_func) (info->stream, "%s,%s",
			       get_reg_name (minfop, x),
			       get_reg_name (minfop, z));
      break;

    case mmix_operands_sync:
	/* Like SYNC - "XYZ".  */
      (*info->fprintf_func) (info->stream, "%u",
			     x * 65536 + y * 256 + z);
      break;

    case mmix_operands_x_regs_z:
      /* Like SYNCD - "X,$Y,$Z|Z".  */
      if (insn & INSN_IMMEDIATE_BIT)
	(*info->fprintf_func) (info->stream, "%d,%s,%d",
			       x, get_reg_name (minfop, y), z);
      else
	(*info->fprintf_func) (info->stream, "%d,%s,%s",
			       x, get_reg_name (minfop, y),
			       get_reg_name (minfop, z));
      break;

    case mmix_operands_neg:
      /* Like NEG and NEGU - "$X,Y,$Z|Z".  */
      if (insn & INSN_IMMEDIATE_BIT)
	(*info->fprintf_func) (info->stream, "%s,%d,%d",
			       get_reg_name (minfop, x), y, z);
      else
	(*info->fprintf_func) (info->stream, "%s,%d,%s",
			       get_reg_name (minfop, x), y,
			       get_reg_name (minfop, z));
      break;

    case mmix_operands_pushj:
    case mmix_operands_regaddr:
      /* Like GETA or branches - "$X,Address".  */
      {
	bfd_signed_vma offset = (y * 256 + z) * 4;

	if (insn & INSN_BACKWARD_OFFSET_BIT)
	  offset -= 65536 * 4;

	info->target = memaddr + offset;

	(*info->fprintf_func) (info->stream, "%s,", get_reg_name (minfop, x));
	(*info->print_address_func) (memaddr + offset, info);
      }
      break;

    case mmix_operands_get:
      /* GET - "X,spec_reg".  */
      (*info->fprintf_func) (info->stream, "%s,%s",
			     get_reg_name (minfop, x),
			     get_spec_reg_name (minfop, z));
      break;

    case mmix_operands_put:
      /* PUT - "spec_reg,$Z|Z".  */
      if (insn & INSN_IMMEDIATE_BIT)
	(*info->fprintf_func) (info->stream, "%s,%d",
			       get_spec_reg_name (minfop, x), z);
      else
	(*info->fprintf_func) (info->stream, "%s,%s",
			       get_spec_reg_name (minfop, x),
			       get_reg_name (minfop, z));
      break;

    case mmix_operands_set:
      /*  Two registers, "$X,$Y".  */
      (*info->fprintf_func) (info->stream, "%s,%s",
			     get_reg_name (minfop, x),
			     get_reg_name (minfop, y));
      break;

    case mmix_operands_save:
      /* SAVE - "$X,0".  */
      (*info->fprintf_func) (info->stream, "%s,0", minfop->reg_name[x]);
      break;

    case mmix_operands_unsave:
      /* UNSAVE - "0,$Z".  */
      (*info->fprintf_func) (info->stream, "0,%s", minfop->reg_name[z]);
      break;

    case mmix_operands_xyz_opt:
      /* Like SWYM or TRAP - "X,Y,Z".  */
      (*info->fprintf_func) (info->stream, "%d,%d,%d", x, y, z);
      break;

    case mmix_operands_resume:
      /* Just "Z", like RESUME.  */
      (*info->fprintf_func) (info->stream, "%d", z);
      break;

    default:
      (*info->fprintf_func) (info->stream, _("*unknown operands type: %d*"),
			     opcodep->operands);
      break;
    }

  return 4;
}
