/* Disassemble MN10300 instructions.
  Copyright (C) 1996-2024 Free Software Foundation, Inc.

  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 program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
  MA 02110-1301, USA.  */

#include "sysdep.h"
#include <stdio.h>
#include "opcode/mn10300.h"
#include "disassemble.h"
#include "opintl.h"

#define HAVE_AM33_2 (info->mach == AM33_2)
#define HAVE_AM33   (info->mach == AM33 || HAVE_AM33_2)
#define HAVE_AM30   (info->mach == AM30)

static void
disassemble (bfd_vma memaddr,
            struct disassemble_info *info,
            unsigned long insn,
            unsigned int size)
{
 struct mn10300_opcode *op = (struct mn10300_opcode *) mn10300_opcodes;
 const struct mn10300_operand *operand;
 bfd_byte buffer[4];
 unsigned long extension = 0;
 int status, match = 0;

 /* Find the opcode.  */
 while (op->name)
   {
     int mysize, extra_shift;

     if (op->format == FMT_S0)
       mysize = 1;
     else if (op->format == FMT_S1
              || op->format == FMT_D0)
       mysize = 2;
     else if (op->format == FMT_S2
              || op->format == FMT_D1)
       mysize = 3;
     else if (op->format == FMT_S4)
       mysize = 5;
     else if (op->format == FMT_D2)
       mysize = 4;
     else if (op->format == FMT_D3)
       mysize = 5;
     else if (op->format == FMT_D4)
       mysize = 6;
     else if (op->format == FMT_D6)
       mysize = 3;
     else if (op->format == FMT_D7 || op->format == FMT_D10)
       mysize = 4;
     else if (op->format == FMT_D8)
       mysize = 6;
     else if (op->format == FMT_D9)
       mysize = 7;
     else
       mysize = 7;

     if ((op->mask & insn) == op->opcode
         && size == (unsigned int) mysize
         && (op->machine == 0
             || (op->machine == AM33_2 && HAVE_AM33_2)
             || (op->machine == AM33 && HAVE_AM33)
             || (op->machine == AM30 && HAVE_AM30)))
       {
         const unsigned char *opindex_ptr;
         unsigned int nocomma;
         int paren = 0;

         if (op->format == FMT_D1 || op->format == FMT_S1)
           extra_shift = 8;
         else if (op->format == FMT_D2 || op->format == FMT_D4
                  || op->format == FMT_S2 || op->format == FMT_S4
                  || op->format == FMT_S6 || op->format == FMT_D5)
           extra_shift = 16;
         else if (op->format == FMT_D7
                  || op->format == FMT_D8
                  || op->format == FMT_D9)
           extra_shift = 8;
         else
           extra_shift = 0;

         if (size == 1 || size == 2)
           extension = 0;

         else if (size == 3
                  && (op->format == FMT_D1
                      || op->opcode == 0xdf0000
                      || op->opcode == 0xde0000))
           extension = 0;

         else if (size == 3
                  && op->format == FMT_D6)
           extension = 0;

         else if (size == 3)
           {
             insn &= 0xff0000;
             status = (*info->read_memory_func) (memaddr + 1, buffer, 2, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }

             insn |= bfd_getl16 (buffer);
             extension = 0;
           }
         else if (size == 4
                  && (op->opcode == 0xfaf80000
                      || op->opcode == 0xfaf00000
                      || op->opcode == 0xfaf40000))
           extension = 0;

         else if (size == 4
                  && (op->format == FMT_D7
                      || op->format == FMT_D10))
           extension = 0;

         else if (size == 4)
           {
             insn &= 0xffff0000;
             status = (*info->read_memory_func) (memaddr + 2, buffer, 2, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }

             insn |= bfd_getl16 (buffer);
             extension = 0;
           }
         else if (size == 5 && op->opcode == 0xdc000000)
           {
             unsigned long temp = 0;

             status = (*info->read_memory_func) (memaddr + 1, buffer, 4, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }
             temp |= bfd_getl32 (buffer);

             insn &= 0xff000000;
             insn |= (temp & 0xffffff00) >> 8;
             extension = temp & 0xff;
           }
         else if (size == 5 && op->format == FMT_D3)
           {
             status = (*info->read_memory_func) (memaddr + 2, buffer, 2, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }
             insn &= 0xffff0000;
             insn |= bfd_getl16 (buffer);

             status = (*info->read_memory_func) (memaddr + 4, buffer, 1, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }
             extension = *(unsigned char *) buffer;
           }
         else if (size == 5)
           {
             unsigned long temp = 0;

             status = (*info->read_memory_func) (memaddr + 1, buffer, 2, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }
             temp |= bfd_getl16 (buffer);

             insn &= 0xff0000ff;
             insn |= temp << 8;

             status = (*info->read_memory_func) (memaddr + 4, buffer, 1, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }
             extension = *(unsigned char *) buffer;
           }
         else if (size == 6 && op->format == FMT_D8)
           {
             insn &= 0xffffff00;
             status = (*info->read_memory_func) (memaddr + 5, buffer, 1, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }
             insn |= *(unsigned char *) buffer;

             status = (*info->read_memory_func) (memaddr + 3, buffer, 2, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }
             extension = bfd_getl16 (buffer);
           }
         else if (size == 6)
           {
             unsigned long temp = 0;

             status = (*info->read_memory_func) (memaddr + 2, buffer, 4, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }
             temp |= bfd_getl32 (buffer);

             insn &= 0xffff0000;
             insn |= (temp >> 16) & 0xffff;
             extension = temp & 0xffff;
           }
         else if (size == 7 && op->format == FMT_D9)
           {
             insn &= 0xffffff00;
             status = (*info->read_memory_func) (memaddr + 3, buffer, 4, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }
             extension = bfd_getl32 (buffer);
             insn |= (extension & 0xff000000) >> 24;
             extension &= 0xffffff;
           }
         else if (size == 7 && op->opcode == 0xdd000000)
           {
             unsigned long temp = 0;

             status = (*info->read_memory_func) (memaddr + 1, buffer, 4, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }
             temp |= bfd_getl32 (buffer);

             insn &= 0xff000000;
             insn |= (temp >> 8) & 0xffffff;
             extension = (temp & 0xff) << 16;

             status = (*info->read_memory_func) (memaddr + 5, buffer, 2, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }
             extension |= bfd_getb16 (buffer);
           }
         else if (size == 7)
           {
             unsigned long temp = 0;

             status = (*info->read_memory_func) (memaddr + 2, buffer, 4, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }
             temp |= bfd_getl32 (buffer);

             insn &= 0xffff0000;
             insn |= (temp >> 16) & 0xffff;
             extension = (temp & 0xffff) << 8;

             status = (*info->read_memory_func) (memaddr + 6, buffer, 1, info);
             if (status != 0)
               {
                 (*info->memory_error_func) (status, memaddr, info);
                 return;
               }
             extension |= *(unsigned char *) buffer;
           }

         match = 1;
         (*info->fprintf_func) (info->stream, "%s\t", op->name);

         /* Now print the operands.  */
         for (opindex_ptr = op->operands, nocomma = 1;
              *opindex_ptr != 0;
              opindex_ptr++)
           {
             unsigned long value;

             operand = &mn10300_operands[*opindex_ptr];

             /* If this operand is a PLUS (autoincrement), then do not emit
                a comma before emitting the plus.  */
             if ((operand->flags & MN10300_OPERAND_PLUS) != 0)
               nocomma = 1;

             if ((operand->flags & MN10300_OPERAND_DREG) != 0
                 || (operand->flags & MN10300_OPERAND_AREG) != 0
                 || (operand->flags & MN10300_OPERAND_RREG) != 0
                 || (operand->flags & MN10300_OPERAND_XRREG) != 0)
               value = ((insn >> (operand->shift + extra_shift))
                        & ((1 << operand->bits) - 1));
             else if ((operand->flags & MN10300_OPERAND_SPLIT) != 0)
               {
                 unsigned long temp;

                 value = insn & ((1 << operand->bits) - 1);
                 value <<= (32 - operand->bits);
                 temp = extension >> operand->shift;
                 temp &= ((1 << (32 - operand->bits)) - 1);
                 value |= temp;
                 value = ((value ^ (((unsigned long) 1) << 31))
                          - (((unsigned long) 1) << 31));
               }
             else if ((operand->flags & MN10300_OPERAND_24BIT) != 0)
               {
                 unsigned long temp;

                 value = insn & ((1 << operand->bits) - 1);
                 value <<= (24 - operand->bits);
                 temp = extension >> operand->shift;
                 temp &= ((1 << (24 - operand->bits)) - 1);
                 value |= temp;
                 if ((operand->flags & MN10300_OPERAND_SIGNED) != 0)
                   value = ((value & 0xffffff) ^ 0x800000) - 0x800000;
               }
             else if ((operand->flags & (MN10300_OPERAND_FSREG
                                         | MN10300_OPERAND_FDREG)))
               {
                 /* See m10300-opc.c just before #define FSM0 for an
                    explanation of these variables.  Note that
                    FMT-implied shifts are not taken into account for
                    FP registers.  */
                 unsigned long mask_low, mask_high;
                 int shl_low, shr_high, shl_high;

                 switch (operand->bits)
                   {
                   case 5:
                     /* Handle regular FP registers.  */
                     if (operand->shift >= 0)
                       {
                         /* This is an `m' register.  */
                         shl_low = operand->shift;
                         shl_high = 8 + (8 & shl_low) + (shl_low & 4) / 4;
                       }
                     else
                       {
                         /* This is an `n' register.  */
                         shl_low = -operand->shift;
                         shl_high = shl_low / 4;
                       }
                     mask_low = 0x0f;
                     mask_high = 0x10;
                     shr_high = 4;
                     break;

                   case 3:
                     /* Handle accumulators.  */
                     shl_low = -operand->shift;
                     shl_high = 0;
                     mask_low = 0x03;
                     mask_high = 0x04;
                     shr_high = 2;
                     break;

                   default:
                     abort ();
                   }
                 value = ((((insn >> shl_high) << shr_high) & mask_high)
                          | ((insn >> shl_low) & mask_low));
               }
             else if ((operand->flags & MN10300_OPERAND_EXTENDED) != 0)
               value = ((extension >> (operand->shift))
                        & ((1 << operand->bits) - 1));

             else
               value = ((insn >> (operand->shift))
                        & ((1 << operand->bits) - 1));

             if ((operand->flags & MN10300_OPERAND_SIGNED) != 0
                 /* These are properly extended by the code above.  */
                 && ((operand->flags & MN10300_OPERAND_24BIT) == 0))
               value = ((value ^ (((unsigned long) 1) << (operand->bits - 1)))
                        - (((unsigned long) 1) << (operand->bits - 1)));

             if (!nocomma
                 && (!paren
                     || ((operand->flags & MN10300_OPERAND_PAREN) == 0)))
               (*info->fprintf_func) (info->stream, ",");

             nocomma = 0;

             if ((operand->flags & MN10300_OPERAND_DREG) != 0)
               (*info->fprintf_func) (info->stream, "d%d", (int) value);

             else if ((operand->flags & MN10300_OPERAND_AREG) != 0)
               (*info->fprintf_func) (info->stream, "a%d", (int) value);

             else if ((operand->flags & MN10300_OPERAND_SP) != 0)
               (*info->fprintf_func) (info->stream, "sp");

             else if ((operand->flags & MN10300_OPERAND_PSW) != 0)
               (*info->fprintf_func) (info->stream, "psw");

             else if ((operand->flags & MN10300_OPERAND_MDR) != 0)
               (*info->fprintf_func) (info->stream, "mdr");

             else if ((operand->flags & MN10300_OPERAND_RREG) != 0)
               {
                 if (value < 8)
                   (*info->fprintf_func) (info->stream, "r%d", (int) value);
                 else if (value < 12)
                   (*info->fprintf_func) (info->stream, "a%d", (int) value - 8);
                 else
                   (*info->fprintf_func) (info->stream, "d%d", (int) value - 12);
               }

             else if ((operand->flags & MN10300_OPERAND_XRREG) != 0)
               {
                 if (value == 0)
                   (*info->fprintf_func) (info->stream, "sp");
                 else
                   (*info->fprintf_func) (info->stream, "xr%d", (int) value);
               }

             else if ((operand->flags & MN10300_OPERAND_FSREG) != 0)
               (*info->fprintf_func) (info->stream, "fs%d", (int) value);

             else if ((operand->flags & MN10300_OPERAND_FDREG) != 0)
               (*info->fprintf_func) (info->stream, "fd%d", (int) value);

             else if ((operand->flags & MN10300_OPERAND_FPCR) != 0)
               (*info->fprintf_func) (info->stream, "fpcr");

             else if ((operand->flags & MN10300_OPERAND_USP) != 0)
               (*info->fprintf_func) (info->stream, "usp");

             else if ((operand->flags & MN10300_OPERAND_SSP) != 0)
               (*info->fprintf_func) (info->stream, "ssp");

             else if ((operand->flags & MN10300_OPERAND_MSP) != 0)
               (*info->fprintf_func) (info->stream, "msp");

             else if ((operand->flags & MN10300_OPERAND_PC) != 0)
               (*info->fprintf_func) (info->stream, "pc");

             else if ((operand->flags & MN10300_OPERAND_EPSW) != 0)
               (*info->fprintf_func) (info->stream, "epsw");

             else if ((operand->flags & MN10300_OPERAND_PLUS) != 0)
               (*info->fprintf_func) (info->stream, "+");

             else if ((operand->flags & MN10300_OPERAND_PAREN) != 0)
               {
                 if (paren)
                   (*info->fprintf_func) (info->stream, ")");
                 else
                   {
                     (*info->fprintf_func) (info->stream, "(");
                     nocomma = 1;
                   }
                 paren = !paren;
               }

             else if ((operand->flags & MN10300_OPERAND_PCREL) != 0)
               (*info->print_address_func) ((long) value + memaddr, info);

             else if ((operand->flags & MN10300_OPERAND_MEMADDR) != 0)
               (*info->print_address_func) (value, info);

             else if ((operand->flags & MN10300_OPERAND_REG_LIST) != 0)
               {
                 int comma = 0;

                 (*info->fprintf_func) (info->stream, "[");
                 if (value & 0x80)
                   {
                     (*info->fprintf_func) (info->stream, "d2");
                     comma = 1;
                   }

                 if (value & 0x40)
                   {
                     if (comma)
                       (*info->fprintf_func) (info->stream, ",");
                     (*info->fprintf_func) (info->stream, "d3");
                     comma = 1;
                   }

                 if (value & 0x20)
                   {
                     if (comma)
                       (*info->fprintf_func) (info->stream, ",");
                     (*info->fprintf_func) (info->stream, "a2");
                     comma = 1;
                   }

                 if (value & 0x10)
                   {
                     if (comma)
                       (*info->fprintf_func) (info->stream, ",");
                     (*info->fprintf_func) (info->stream, "a3");
                     comma = 1;
                   }

                 if (value & 0x08)
                   {
                     if (comma)
                       (*info->fprintf_func) (info->stream, ",");
                     (*info->fprintf_func) (info->stream, "other");
                     comma = 1;
                   }

                 if (value & 0x04)
                   {
                     if (comma)
                       (*info->fprintf_func) (info->stream, ",");
                     (*info->fprintf_func) (info->stream, "exreg0");
                     comma = 1;
                   }
                 if (value & 0x02)
                   {
                     if (comma)
                       (*info->fprintf_func) (info->stream, ",");
                     (*info->fprintf_func) (info->stream, "exreg1");
                     comma = 1;
                   }
                 if (value & 0x01)
                   {
                     if (comma)
                       (*info->fprintf_func) (info->stream, ",");
                     (*info->fprintf_func) (info->stream, "exother");
                     comma = 1;
                   }
                 (*info->fprintf_func) (info->stream, "]");
               }

             else
               (*info->fprintf_func) (info->stream, "%ld", (long) value);
           }
         /* All done. */
         break;
       }
     op++;
   }

 if (!match)
   /* xgettext:c-format */
   (*info->fprintf_func) (info->stream, _("unknown\t0x%04lx"), insn);
}

int
print_insn_mn10300 (bfd_vma memaddr, struct disassemble_info *info)
{
 int status;
 bfd_byte buffer[4];
 unsigned long insn;
 unsigned int consume;

 /* First figure out how big the opcode is.  */
 status = (*info->read_memory_func) (memaddr, buffer, 1, info);
 if (status != 0)
   {
     (*info->memory_error_func) (status, memaddr, info);
     return -1;
   }
 insn = *(unsigned char *) buffer;

 /* These are one byte insns.  */
 if ((insn & 0xf3) == 0x00
     || (insn & 0xf0) == 0x10
     || (insn & 0xfc) == 0x3c
     || (insn & 0xf3) == 0x41
     || (insn & 0xf3) == 0x40
     || (insn & 0xfc) == 0x50
     || (insn & 0xfc) == 0x54
     || (insn & 0xf0) == 0x60
     || (insn & 0xf0) == 0x70
     || ((insn & 0xf0) == 0x80
         && (insn & 0x0c) >> 2 != (insn & 0x03))
     || ((insn & 0xf0) == 0x90
         && (insn & 0x0c) >> 2 != (insn & 0x03))
     || ((insn & 0xf0) == 0xa0
         && (insn & 0x0c) >> 2 != (insn & 0x03))
     || ((insn & 0xf0) == 0xb0
         && (insn & 0x0c) >> 2 != (insn & 0x03))
     || (insn & 0xff) == 0xcb
     || (insn & 0xfc) == 0xd0
     || (insn & 0xfc) == 0xd4
     || (insn & 0xfc) == 0xd8
     || (insn & 0xf0) == 0xe0
     || (insn & 0xff) == 0xff)
   {
     consume = 1;
   }

 /* These are two byte insns.  */
 else if ((insn & 0xf0) == 0x80
          || (insn & 0xf0) == 0x90
          || (insn & 0xf0) == 0xa0
          || (insn & 0xf0) == 0xb0
          || (insn & 0xfc) == 0x20
          || (insn & 0xfc) == 0x28
          || (insn & 0xf3) == 0x43
          || (insn & 0xf3) == 0x42
          || (insn & 0xfc) == 0x58
          || (insn & 0xfc) == 0x5c
          || ((insn & 0xf0) == 0xc0
              && (insn & 0xff) != 0xcb
              && (insn & 0xff) != 0xcc
              && (insn & 0xff) != 0xcd)
          || (insn & 0xff) == 0xf0
          || (insn & 0xff) == 0xf1
          || (insn & 0xff) == 0xf2
          || (insn & 0xff) == 0xf3
          || (insn & 0xff) == 0xf4
          || (insn & 0xff) == 0xf5
          || (insn & 0xff) == 0xf6)
   {
     status = (*info->read_memory_func) (memaddr, buffer, 2, info);
     if (status != 0)
       {
         (*info->memory_error_func) (status, memaddr, info);
         return -1;
       }
     insn = bfd_getb16 (buffer);
     consume = 2;
   }

 /* These are three byte insns.  */
 else if ((insn & 0xff) == 0xf8
          || (insn & 0xff) == 0xcc
          || (insn & 0xff) == 0xf9
          || (insn & 0xf3) == 0x01
          || (insn & 0xf3) == 0x02
          || (insn & 0xf3) == 0x03
          || (insn & 0xfc) == 0x24
          || (insn & 0xfc) == 0x2c
          || (insn & 0xfc) == 0x30
          || (insn & 0xfc) == 0x34
          || (insn & 0xfc) == 0x38
          || (insn & 0xff) == 0xde
          || (insn & 0xff) == 0xdf
          || (insn & 0xff) == 0xf9
          || (insn & 0xff) == 0xcc)
   {
     status = (*info->read_memory_func) (memaddr, buffer, 2, info);
     if (status != 0)
       {
         (*info->memory_error_func) (status, memaddr, info);
         return -1;
       }
     insn = bfd_getb16 (buffer);
     insn <<= 8;
     status = (*info->read_memory_func) (memaddr + 2, buffer, 1, info);
     if (status != 0)
       {
         (*info->memory_error_func) (status, memaddr, info);
         return -1;
       }
     insn |= *(unsigned char *) buffer;
     consume = 3;
   }

 /* These are four byte insns.  */
 else if ((insn & 0xff) == 0xfa
          || (insn & 0xff) == 0xf7
          || (insn & 0xff) == 0xfb)
   {
     status = (*info->read_memory_func) (memaddr, buffer, 4, info);
     if (status != 0)
       {
         (*info->memory_error_func) (status, memaddr, info);
         return -1;
       }
     insn = bfd_getb32 (buffer);
     consume = 4;
   }

 /* These are five byte insns.  */
 else if ((insn & 0xff) == 0xcd
          || (insn & 0xff) == 0xdc)
   {
     status = (*info->read_memory_func) (memaddr, buffer, 4, info);
     if (status != 0)
       {
         (*info->memory_error_func) (status, memaddr, info);
         return -1;
       }
     insn = bfd_getb32 (buffer);
     consume = 5;
   }

 /* These are six byte insns.  */
 else if ((insn & 0xff) == 0xfd
          || (insn & 0xff) == 0xfc)
   {
     status = (*info->read_memory_func) (memaddr, buffer, 4, info);
     if (status != 0)
       {
         (*info->memory_error_func) (status, memaddr, info);
         return -1;
       }

     insn = bfd_getb32 (buffer);
     consume = 6;
   }

 /* Else its a seven byte insns (in theory).  */
 else
   {
     status = (*info->read_memory_func) (memaddr, buffer, 4, info);
     if (status != 0)
       {
         (*info->memory_error_func) (status, memaddr, info);
         return -1;
       }

     insn = bfd_getb32 (buffer);
     consume = 7;
     /* Handle the 5-byte extended instruction codes.  */
     if ((insn & 0xfff80000) == 0xfe800000)
       consume = 5;
   }

 disassemble (memaddr, info, insn, consume);

 return consume;
}