/* Single instruction disassembler for the Visium.

  Copyright (C) 2002-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 "disassemble.h"
#include "opcode/visium.h"

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

/* Maximum length of an instruction.  */
#define MAXLEN 4

struct private
{
 /* Points to first byte not fetched.  */
 bfd_byte *max_fetched;
 bfd_byte the_buffer[MAXLEN];
 bfd_vma insn_start;
 jmp_buf bailout;
};

/* Make sure that bytes from INFO->PRIVATE_DATA->BUFFER (inclusive)
  to ADDR (exclusive) are valid.  Returns 1 for success, longjmps
  on error.  */
#define FETCH_DATA(info, addr) \
 ((addr) <= ((struct private *)(info->private_data))->max_fetched \
  ? 1 : fetch_data ((info), (addr)))

static int fetch_data (struct disassemble_info *info, bfd_byte * addr);

static int
fetch_data (struct disassemble_info *info, bfd_byte *addr)
{
 int status;
 struct private *priv = (struct private *) info->private_data;
 bfd_vma start = priv->insn_start + (priv->max_fetched - priv->the_buffer);

 status = (*info->read_memory_func) (start,
                                     priv->max_fetched,
                                     addr - priv->max_fetched, info);
 if (status != 0)
   {
     (*info->memory_error_func) (status, start, info);
     longjmp (priv->bailout, 1);
   }
 else
   priv->max_fetched = addr;
 return 1;
}

static char *size_names[] = { "?", "b", "w", "?", "l", "?", "?", "?" };

static char *cc_names[] =
{
 "fa", "eq", "cs", "os", "ns", "ne", "cc", "oc",
 "nc", "ge", "gt", "hi", "le", "ls", "lt", "tr"
};

/* Disassemble non-storage relative instructions.  */

static int
disassem_class0 (disassemble_info *info, unsigned int ins)
{
 int opcode = (ins >> 21) & 0x000f;

 if (ins & CLASS0_UNUSED_MASK)
   goto illegal_opcode;

 switch (opcode)
   {
   case 0:
     /* BRR instruction.  */
     {
       unsigned cbf = (ins >> 27) & 0x000f;
       int displacement = ((ins & 0xffff) ^ 0x8000) - 0x8000;

       if (ins == 0)
         (*info->fprintf_func) (info->stream, "nop");
       else
         (*info->fprintf_func) (info->stream, "brr     %s,%+d",
                                cc_names[cbf], displacement);
     }
     break;
   case 1:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 2:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 3:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 4:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 5:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 6:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 7:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 8:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 9:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 10:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 11:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 12:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 13:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 14:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 15:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   }
 return 0;

illegal_opcode:
 return -1;
}

/* Disassemble non-storage register class instructions.   */

static int
disassem_class1 (disassemble_info *info, unsigned int ins)
{
 int opcode = (ins >> 21) & 0xf;
 int source_a = (ins >> 16) & 0x1f;
 int source_b = (ins >> 4) & 0x1f;
 int indx = (ins >> 10) & 0x1f;

 int size = ins & 0x7;

 if (ins & CLASS1_UNUSED_MASK)
   goto illegal_opcode;

 switch (opcode)
   {
   case 0:
     /* Stop.  */
     (*info->fprintf_func) (info->stream, "stop    %d,r%d", indx, source_a);
     break;
   case 1:
     /* BMI - Block Move Indirect.  */
     if (ins != BMI)
       goto illegal_opcode;

     (*info->fprintf_func) (info->stream, "bmi     r1,r2,r3");
     break;
   case 2:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 3:
     /* BMD - Block Move Direct.  */
     if (ins != BMD)
       goto illegal_opcode;

     (*info->fprintf_func) (info->stream, "bmd     r1,r2,r3");
     break;
   case 4:
     /* DSI - Disable Interrupts.  */
     if (ins != DSI)
       goto illegal_opcode;

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

   case 5:
     /* ENI - Enable Interrupts.  */
     if (ins != ENI)
       goto illegal_opcode;

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

   case 6:
     /* Illegal opcode (was EUT).  */
     goto illegal_opcode;
     break;
   case 7:
     /* RFI - Return from Interrupt.  */
     if (ins != RFI)
       goto illegal_opcode;

     (*info->fprintf_func) (info->stream, "rfi");
     break;
   case 8:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 9:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 10:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 11:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 12:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 13:
     goto illegal_opcode;
     break;
   case 14:
     goto illegal_opcode;
     break;
   case 15:
     if (ins & EAM_SELECT_MASK)
       {
         /* Extension arithmetic module write */
         int fp_ins = (ins >> 27) & 0xf;

         if (size != 4)
           goto illegal_opcode;

         if (ins & FP_SELECT_MASK)
           {
             /* Which floating point instructions don't need a fsrcB
                register.  */
             const int no_fsrcb[16] = { 1, 0, 0, 0, 0, 1, 1, 1,
               1, 1, 0, 0, 1, 0, 0, 0
             };
             if (no_fsrcb[fp_ins] && source_b)
               goto illegal_opcode;

             /* Check that none of the floating register register numbers
                is higher than 15. (If this is fload, then srcA is a
                general register.  */
             if (ins & ((1 << 14) | (1 << 8)) || (fp_ins && ins & (1 << 20)))
               goto illegal_opcode;

             switch (fp_ins)
               {
               case 0:
                 (*info->fprintf_func) (info->stream, "fload   f%d,r%d",
                                        indx, source_a);
                 break;
               case 1:
                 (*info->fprintf_func) (info->stream, "fadd    f%d,f%d,f%d",
                                        indx, source_a, source_b);
                 break;
               case 2:
                 (*info->fprintf_func) (info->stream, "fsub    f%d,f%d,f%d",
                                        indx, source_a, source_b);
                 break;
               case 3:
                 (*info->fprintf_func) (info->stream, "fmult   f%d,f%d,f%d",
                                        indx, source_a, source_b);
                 break;
               case 4:
                 (*info->fprintf_func) (info->stream, "fdiv    f%d,f%d,f%d",
                                        indx, source_a, source_b);
                 break;
               case 5:
                 (*info->fprintf_func) (info->stream, "fsqrt   f%d,f%d",
                                        indx, source_a);
                 break;
               case 6:
                 (*info->fprintf_func) (info->stream, "fneg    f%d,f%d",
                                        indx, source_a);
                 break;
               case 7:
                 (*info->fprintf_func) (info->stream, "fabs    f%d,f%d",
                                        indx, source_a);
                 break;
               case 8:
                 (*info->fprintf_func) (info->stream, "ftoi    f%d,f%d",
                                        indx, source_a);
                 break;
               case 9:
                 (*info->fprintf_func) (info->stream, "itof    f%d,f%d",
                                        indx, source_a);
                 break;
               case 12:
                 (*info->fprintf_func) (info->stream, "fmove   f%d,f%d",
                                        indx, source_a);
                 break;
               default:
                 (*info->fprintf_func) (info->stream,
                                        "fpinst  %d,f%d,f%d,f%d", fp_ins,
                                        indx, source_a, source_b);
                 break;
               }
           }
         else
           {
             /* Which EAM operations do not need a srcB register.  */
             const int no_srcb[32] =
             { 0, 0, 1, 1, 0, 1, 1, 1,
               0, 1, 1, 1, 0, 0, 0, 0,
               0, 0, 0, 0, 0, 0, 0, 0,
               0, 0, 0, 0, 0, 0, 0, 0
             };

             if (no_srcb[indx] && source_b)
               goto illegal_opcode;

             if (fp_ins)
               goto illegal_opcode;

             switch (indx)
               {
               case 0:
                 (*info->fprintf_func) (info->stream, "mults   r%d,r%d",
                                        source_a, source_b);
                 break;
               case 1:
                 (*info->fprintf_func) (info->stream, "multu   r%d,r%d",
                                        source_a, source_b);
                 break;
               case 2:
                 (*info->fprintf_func) (info->stream, "divs    r%d",
                                        source_a);
                 break;
               case 3:
                 (*info->fprintf_func) (info->stream, "divu    r%d",
                                        source_a);
                 break;
               case 4:
                 (*info->fprintf_func) (info->stream, "writemd r%d,r%d",
                                        source_a, source_b);
                 break;
               case 5:
                 (*info->fprintf_func) (info->stream, "writemdc r%d",
                                        source_a);
                 break;
               case 6:
                 (*info->fprintf_func) (info->stream, "divds   r%d",
                                        source_a);
                 break;
               case 7:
                 (*info->fprintf_func) (info->stream, "divdu   r%d",
                                        source_a);
                 break;
               case 9:
                 (*info->fprintf_func) (info->stream, "asrd    r%d",
                                        source_a);
                 break;
               case 10:
                 (*info->fprintf_func) (info->stream, "lsrd    r%d",
                                        source_a);
                 break;
               case 11:
                 (*info->fprintf_func) (info->stream, "asld    r%d",
                                        source_a);
                 break;
               default:
                 (*info->fprintf_func) (info->stream,
                                        "eamwrite %d,r%d,r%d", indx,
                                        source_a, source_b);
                 break;
               }
           }
       }
     else
       {
         /* WRITE - write to memory.  */
         (*info->fprintf_func) (info->stream, "write.%s %d(r%d),r%d",
                                size_names[size], indx, source_a, source_b);
       }
     break;
   }

 return 0;

illegal_opcode:
 return -1;
}

/* Disassemble storage immediate class instructions.   */

static int
disassem_class2 (disassemble_info *info, unsigned int ins)
{
 int opcode = (ins >> 21) & 0xf;
 int source_a = (ins >> 16) & 0x1f;
 unsigned immediate = ins & 0x0000ffff;

 if (ins & CC_MASK)
   goto illegal_opcode;

 switch (opcode)
   {
   case 0:
     /* ADDI instruction.  */
     (*info->fprintf_func) (info->stream, "addi    r%d,%d", source_a,
                            immediate);
     break;
   case 1:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 2:
     /* SUBI instruction.  */
     (*info->fprintf_func) (info->stream, "subi    r%d,%d", source_a,
                            immediate);
     break;
   case 3:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 4:
     /* MOVIL instruction.  */
     (*info->fprintf_func) (info->stream, "movil   r%d,0x%04X", source_a,
                            immediate);
     break;
   case 5:
     /* MOVIU instruction.  */
     (*info->fprintf_func) (info->stream, "moviu   r%d,0x%04X", source_a,
                            immediate);
     break;
   case 6:
     /* MOVIQ instruction.  */
     (*info->fprintf_func) (info->stream, "moviq   r%d,%u", source_a,
                            immediate);
     break;
   case 7:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 8:
     /* WRTL instruction.  */
     if (source_a != 0)
       goto illegal_opcode;

     (*info->fprintf_func) (info->stream, "wrtl    0x%04X", immediate);
     break;
   case 9:
     /* WRTU instruction.  */
     if (source_a != 0)
       goto illegal_opcode;

     (*info->fprintf_func) (info->stream, "wrtu    0x%04X", immediate);
     break;
   case 10:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 11:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 12:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 13:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 14:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   case 15:
     /* Illegal opcode.  */
     goto illegal_opcode;
     break;
   }

 return 0;

illegal_opcode:
 return -1;
}

/* Disassemble storage register class instructions.  */

static int
disassem_class3 (disassemble_info *info, unsigned int ins)
{
 int opcode = (ins >> 21) & 0xf;
 int source_b = (ins >> 4) & 0x1f;
 int source_a = (ins >> 16) & 0x1f;
 int size = ins & 0x7;
 int dest = (ins >> 10) & 0x1f;

 /* Those instructions that don't have a srcB register.  */
 const int no_srcb[16] =
 { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 };

 /* These are instructions which can take an immediate srcB value.  */
 const int srcb_immed[16] =
 { 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1 };

 /* User opcodes should not provide a non-zero srcB register
    when none is required. Only a BRA or floating point
    instruction should have a non-zero condition code field.
    Only a WRITE or EAMWRITE (opcode 15) should select an EAM
    or floating point operation.  Note that FP_SELECT_MASK is
    the same bit (bit 3) as the interrupt bit which
    distinguishes SYS1 from BRA and SYS2 from RFLAG.  */
 if ((no_srcb[opcode] && source_b)
     || (!srcb_immed[opcode] && ins & CLASS3_SOURCEB_IMMED)
     || (opcode != 12 && opcode != 15 && ins & CC_MASK)
     || (opcode != 15 && ins & (EAM_SELECT_MASK | FP_SELECT_MASK)))
   goto illegal_opcode;


 switch (opcode)
   {
   case 0:
     /* ADD instruction.  */
     (*info->fprintf_func) (info->stream, "add.%s   r%d,r%d,r%d",
                            size_names[size], dest, source_a, source_b);
     break;
   case 1:
     /* ADC instruction.  */
     (*info->fprintf_func) (info->stream, "adc.%s   r%d,r%d,r%d",
                            size_names[size], dest, source_a, source_b);
     break;
   case 2:
     /* SUB instruction.  */
     if (dest == 0)
       (*info->fprintf_func) (info->stream, "cmp.%s   r%d,r%d",
                              size_names[size], source_a, source_b);
     else
       (*info->fprintf_func) (info->stream, "sub.%s   r%d,r%d,r%d",
                              size_names[size], dest, source_a, source_b);
     break;
   case 3:
     /* SUBC instruction.  */
     if (dest == 0)
       (*info->fprintf_func) (info->stream, "cmpc.%s  r%d,r%d",
                              size_names[size], source_a, source_b);
     else
       (*info->fprintf_func) (info->stream, "subc.%s  r%d,r%d,r%d",
                              size_names[size], dest, source_a, source_b);
     break;
   case 4:
     /* EXTW instruction.  */
     if (size == 1)
       goto illegal_opcode;

     (*info->fprintf_func) (info->stream, "extw.%s  r%d,r%d",
                            size_names[size], dest, source_a);
     break;
   case 5:
     /* ASR instruction.  */
     if (ins & CLASS3_SOURCEB_IMMED)
       (*info->fprintf_func) (info->stream, "asr.%s   r%d,r%d,%d",
                              size_names[size], dest, source_a, source_b);
     else
       (*info->fprintf_func) (info->stream, "asr.%s   r%d,r%d,r%d",
                              size_names[size], dest, source_a, source_b);
     break;
   case 6:
     /* LSR instruction.  */
     if (ins & CLASS3_SOURCEB_IMMED)
       (*info->fprintf_func) (info->stream, "lsr.%s   r%d,r%d,%d",
                              size_names[size], dest, source_a, source_b);
     else
       (*info->fprintf_func) (info->stream, "lsr.%s   r%d,r%d,r%d",
                              size_names[size], dest, source_a, source_b);
     break;
   case 7:
     /* ASL instruction.  */
     if (ins & CLASS3_SOURCEB_IMMED)
       (*info->fprintf_func) (info->stream, "asl.%s   r%d,r%d,%d",
                              size_names[size], dest, source_a, source_b);
     else
       (*info->fprintf_func) (info->stream, "asl.%s   r%d,r%d,r%d",
                              size_names[size], dest, source_a, source_b);
     break;
   case 8:
     /* XOR instruction.  */
     (*info->fprintf_func) (info->stream, "xor.%s   r%d,r%d,r%d",
                            size_names[size], dest, source_a, source_b);
     break;
   case 9:
     /* OR instruction.  */
     if (source_b == 0)
       (*info->fprintf_func) (info->stream, "move.%s  r%d,r%d",
                              size_names[size], dest, source_a);
     else
       (*info->fprintf_func) (info->stream, "or.%s    r%d,r%d,r%d",
                              size_names[size], dest, source_a, source_b);
     break;
   case 10:
     /* AND instruction.  */
     (*info->fprintf_func) (info->stream, "and.%s   r%d,r%d,r%d",
                            size_names[size], dest, source_a, source_b);
     break;
   case 11:
     /* NOT instruction.  */
     (*info->fprintf_func) (info->stream, "not.%s   r%d,r%d",
                            size_names[size], dest, source_a);
     break;
   case 12:
     /* BRA instruction.  */
     {
       unsigned cbf = (ins >> 27) & 0x000f;

       if (size != 4)
         goto illegal_opcode;

       (*info->fprintf_func) (info->stream, "bra     %s,r%d,r%d",
                              cc_names[cbf], source_a, dest);
     }
     break;
   case 13:
     /* RFLAG instruction.  */
     if (source_a || size != 4)
       goto illegal_opcode;

     (*info->fprintf_func) (info->stream, "rflag   r%d", dest);
     break;
   case 14:
     /* EXTB instruction.  */
     (*info->fprintf_func) (info->stream, "extb.%s  r%d,r%d",
                            size_names[size], dest, source_a);
     break;
   case 15:
     if (!(ins & CLASS3_SOURCEB_IMMED))
       goto illegal_opcode;

     if (ins & EAM_SELECT_MASK)
       {
         /* Extension arithmetic module read.  */
         int fp_ins = (ins >> 27) & 0xf;

         if (size != 4)
           goto illegal_opcode;

         if (ins & FP_SELECT_MASK)
           {
             /* Check fsrcA <= 15 and fsrcB <= 15.  */
             if (ins & ((1 << 20) | (1 << 8)))
               goto illegal_opcode;

             switch (fp_ins)
               {
               case 0:
                 if (source_b)
                   goto illegal_opcode;

                 (*info->fprintf_func) (info->stream, "fstore  r%d,f%d",
                                        dest, source_a);
                 break;
               case 10:
                 (*info->fprintf_func) (info->stream, "fcmp    r%d,f%d,f%d",
                                        dest, source_a, source_b);
                 break;
               case 11:
                 (*info->fprintf_func) (info->stream, "fcmpe   r%d,f%d,f%d",
                                        dest, source_a, source_b);
                 break;
               default:
                 (*info->fprintf_func) (info->stream,
                                        "fpuread %d,r%d,f%d,f%d", fp_ins,
                                        dest, source_a, source_b);
                 break;
               }
           }
         else
           {
             if (fp_ins || source_a)
               goto illegal_opcode;

             switch (source_b)
               {
               case 0:
                 (*info->fprintf_func) (info->stream, "readmda r%d", dest);
                 break;
               case 1:
                 (*info->fprintf_func) (info->stream, "readmdb r%d", dest);
                 break;
               case 2:
                 (*info->fprintf_func) (info->stream, "readmdc r%d", dest);
                 break;
               default:
                 (*info->fprintf_func) (info->stream, "eamread r%d,%d",
                                        dest, source_b);
                 break;
               }
           }
       }
     else
       {
         if (ins & FP_SELECT_MASK)
           goto illegal_opcode;

         /* READ instruction.  */
         (*info->fprintf_func) (info->stream, "read.%s  r%d,%d(r%d)",
                                size_names[size], dest, source_b, source_a);
       }
     break;
   }

 return 0;

illegal_opcode:
 return -1;

}

/* Print the visium instruction at address addr in debugged memory,
  on info->stream. Return length of the instruction, in bytes.  */

int
print_insn_visium (bfd_vma addr, disassemble_info *info)
{
 unsigned ins;
 unsigned p1, p2;
 int ans;
 int i;

 /* Stuff copied from m68k-dis.c.  */
 struct private priv;
 bfd_byte *buffer = priv.the_buffer;
 info->private_data = &priv;
 priv.max_fetched = priv.the_buffer;
 priv.insn_start = addr;
 if (setjmp (priv.bailout) != 0)
   {
     /* Error return.  */
     return -1;
   }

 /* We do return this info.  */
 info->insn_info_valid = 1;

 /* Assume non branch insn.  */
 info->insn_type = dis_nonbranch;

 /* Assume no delay.  */
 info->branch_delay_insns = 0;

 /* Assume no target known.  */
 info->target = 0;

 /* Get 32-bit instruction word.  */
 FETCH_DATA (info, buffer + 4);
 ins = (unsigned) buffer[0] << 24;
 ins |= buffer[1] << 16;
 ins |= buffer[2] << 8;
 ins |= buffer[3];

 ans = 0;

 p1 = buffer[0] ^ buffer[1] ^ buffer[2] ^ buffer[3];
 p2 = 0;
 for (i = 0; i < 8; i++)
   {
     p2 += p1 & 1;
     p1 >>= 1;
   }

 /* Decode the instruction.  */
 if (p2 & 1)
   ans = -1;
 else
   {
     switch ((ins >> 25) & 0x3)
       {
       case 0:
         ans = disassem_class0 (info, ins);
         break;
       case 1:
         ans = disassem_class1 (info, ins);
         break;
       case 2:
         ans = disassem_class2 (info, ins);
         break;
       case 3:
         ans = disassem_class3 (info, ins);
         break;
       }
   }

 if (ans != 0)
   (*info->fprintf_func) (info->stream, "err");

 /* Return number of bytes consumed (always 4 for the Visium).  */
 return 4;
}