/* s12z-dis.c -- Freescale S12Z disassembly
  Copyright (C) 2018-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 <stdint.h>
#include <stdbool.h>
#include <assert.h>

#include "opcode/s12z.h"
#include "bfd.h"
#include "dis-asm.h"
#include "disassemble.h"
#include "s12z-opc.h"
#include "opintl.h"

struct mem_read_abstraction
{
 struct mem_read_abstraction_base base;
 bfd_vma memaddr;
 struct disassemble_info* info;
};

static void
advance (struct mem_read_abstraction_base *b)
{
 struct mem_read_abstraction *mra = (struct mem_read_abstraction *) b;
 mra->memaddr ++;
}

static bfd_vma
posn (struct mem_read_abstraction_base *b)
{
 struct mem_read_abstraction *mra = (struct mem_read_abstraction *) b;
 return mra->memaddr;
}

static int
abstract_read_memory (struct mem_read_abstraction_base *b,
                     int offset,
                     size_t n, bfd_byte *bytes)
{
 struct mem_read_abstraction *mra = (struct mem_read_abstraction *) b;

 int status = (*mra->info->read_memory_func) (mra->memaddr + offset,
                                              bytes, n, mra->info);
 if (status != 0)
   (*mra->info->memory_error_func) (status, mra->memaddr + offset,
                                    mra->info);
 return status != 0 ? -1 : 0;
}

/* Start of disassembly file.  */
const struct reg registers[S12Z_N_REGISTERS] =
 {
   {"d2", 2},
   {"d3", 2},
   {"d4", 2},
   {"d5", 2},

   {"d0", 1},
   {"d1", 1},

   {"d6", 4},
   {"d7", 4},

   {"x", 3},
   {"y", 3},
   {"s", 3},
   {"p", 3},
   {"cch", 1},
   {"ccl", 1},
   {"ccw", 2}
 };

static const char *mnemonics[] =
 {
   "!!invalid!!",
   "psh",
   "pul",
   "tbne", "tbeq", "tbpl", "tbmi", "tbgt", "tble",
   "dbne", "dbeq", "dbpl", "dbmi", "dbgt", "dble",
   "sex",
   "exg",
   "lsl", "lsr",
   "asl", "asr",
   "rol", "ror",
   "bfins", "bfext",

   "trap",

   "ld",
   "st",
   "cmp",

   "stop",
   "wai",
   "sys",

   "minu",
   "mins",
   "maxu",
   "maxs",

   "abs",
   "adc",
   "bit",
   "sbc",
   "rti",
   "clb",
   "eor",

   "sat",

   "nop",
   "bgnd",
   "brclr",
   "brset",
   "rts",
   "lea",
   "mov",

   "bra",
   "bsr",
   "bhi",
   "bls",
   "bcc",
   "bcs",
   "bne",
   "beq",
   "bvc",
   "bvs",
   "bpl",
   "bmi",
   "bge",
   "blt",
   "bgt",
   "ble",
   "inc",
   "clr",
   "dec",

   "add",
   "sub",
   "and",
   "or",

   "tfr",
   "jmp",
   "jsr",
   "com",
   "andcc",
   "neg",
   "orcc",
   "bclr",
   "bset",
   "btgl",
   "swi",

   "mulu",
   "divu",
   "modu",
   "macu",
   "qmulu",

   "muls",
   "divs",
   "mods",
   "macs",
   "qmuls",

   NULL
 };


static void
operand_separator (struct disassemble_info *info)
{
 if ((info->flags & 0x2))
   (*info->fprintf_func) (info->stream, ",");

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

 info->flags |= 0x2;
}

/* Render the symbol name whose value is ADDR + BASE or the adddress itself if
  there is no symbol.  If BASE is non zero, then the a PC relative adddress is
  assumend (ie BASE is the value in the PC.  */
static void
decode_possible_symbol (bfd_signed_vma addr, bfd_vma base,
                       struct disassemble_info *info, bool relative)
{
 const char *fmt = relative ? "*%+" PRId64 : "%" PRId64;
 asymbol *sym = info->symbol_at_address_func (addr + base, info);

 if (!sym)
   (*info->fprintf_func) (info->stream, fmt, (int64_t) addr);
 else
   (*info->fprintf_func) (info->stream, "%s", bfd_asymbol_name (sym));
}


/* Emit the disassembled text for OPR */
static void
opr_emit_disassembly (const struct operand *opr,
                     struct disassemble_info *info)
{
 operand_separator (info);

 switch (opr->cl)
   {
   case OPND_CL_IMMEDIATE:
     (*info->fprintf_func) (info->stream, "#%d",
                            ((struct immediate_operand *) opr)->value);
     break;
   case OPND_CL_REGISTER:
     {
       int r = ((struct register_operand*) opr)->reg;

       if (r < 0 || r >= S12Z_N_REGISTERS)
         (*info->fprintf_func) (info->stream, _("<illegal reg num>"));
       else
         (*info->fprintf_func) (info->stream, "%s", registers[r].name);
     }
     break;
   case OPND_CL_REGISTER_ALL16:
     (*info->fprintf_func) (info->stream, "%s", "ALL16b");
     break;
   case OPND_CL_REGISTER_ALL:
     (*info->fprintf_func) (info->stream, "%s", "ALL");
     break;
   case OPND_CL_BIT_FIELD:
     (*info->fprintf_func) (info->stream, "#%d:%d",
                            ((struct bitfield_operand*)opr)->width,
                            ((struct bitfield_operand*)opr)->offset);
     break;
   case OPND_CL_SIMPLE_MEMORY:
     {
       struct simple_memory_operand *mo =
         (struct simple_memory_operand *) opr;
       decode_possible_symbol (mo->addr, mo->base, info, mo->relative);
     }
     break;
   case OPND_CL_MEMORY:
     {
       int used_reg = 0;
       struct memory_operand *mo = (struct memory_operand *) opr;
       (*info->fprintf_func) (info->stream, "%c", mo->indirect ? '[' : '(');

       const char *fmt;
       assert (mo->mutation == OPND_RM_NONE || mo->n_regs == 1);
       switch (mo->mutation)
         {
         case OPND_RM_PRE_DEC:
           fmt = "-%s";
           break;
         case OPND_RM_PRE_INC:
           fmt = "+%s";
           break;
         case OPND_RM_POST_DEC:
           fmt = "%s-";
           break;
         case OPND_RM_POST_INC:
           fmt = "%s+";
           break;
         case OPND_RM_NONE:
         default:
           if (mo->n_regs < 2)
             (*info->fprintf_func) (info->stream, (mo->n_regs == 0) ? "%d" : "%d,", mo->base_offset);
           fmt = "%s";
           break;
         }
       if (mo->n_regs > 0)
         {
           int r = mo->regs[0];

           if (r < 0 || r >= S12Z_N_REGISTERS)
             (*info->fprintf_func) (info->stream, fmt, _("<illegal reg num>"));
           else
             (*info->fprintf_func) (info->stream, fmt, registers[r].name);
         }
       used_reg = 1;

       if (mo->n_regs > used_reg)
         {
           int r = mo->regs[used_reg];

           if (r < 0 || r >= S12Z_N_REGISTERS)
             (*info->fprintf_func) (info->stream, _("<illegal reg num>"));
           else
             (*info->fprintf_func) (info->stream, ",%s",
                                    registers[r].name);
         }

       (*info->fprintf_func) (info->stream, "%c",
                              mo->indirect ? ']' : ')');
     }
     break;
   };
}

#define S12Z_N_SIZES 4
static const char shift_size_table[S12Z_N_SIZES] =
{
 'b', 'w', 'p', 'l'
};

int
print_insn_s12z (bfd_vma memaddr, struct disassemble_info* info)
{
 int o;
 enum optr operator = OP_INVALID;
 int n_operands = 0;

 /* The longest instruction in S12Z can have 6 operands.
    (Most have 3 or less.  Only PSH and PUL have so many.  */
 struct operand *operands[6];

 struct mem_read_abstraction mra;
 mra.base.read = (void *) abstract_read_memory ;
 mra.base.advance = advance ;
 mra.base.posn = posn;
 mra.memaddr = memaddr;
 mra.info = info;

 short osize = -1;
 int n_bytes =
   decode_s12z (&operator, &osize, &n_operands, operands,
                (struct mem_read_abstraction_base *) &mra);

 (info->fprintf_func) (info->stream, "%s", mnemonics[(long)operator]);

 /* Ship out size sufficies for those instructions which
    need them.  */
 if (osize == -1)
   {
     bool suffix = false;

     for (o = 0; o < n_operands; ++o)
       {
         if (operands[o] && operands[o]->osize != -1)
           {
             if (!suffix)
               {
                 (*mra.info->fprintf_func) (mra.info->stream, "%c", '.');
                 suffix = true;
               }

             osize = operands[o]->osize;

             if (osize < 0 || osize >= S12Z_N_SIZES)
               (*mra.info->fprintf_func) (mra.info->stream, _("<bad>"));
             else
               (*mra.info->fprintf_func) (mra.info->stream, "%c",
                                          shift_size_table[osize]);
           }
       }
   }
 else
   {
     if (osize < 0 || osize >= S12Z_N_SIZES)
       (*mra.info->fprintf_func) (mra.info->stream, _(".<bad>"));
     else
       (*mra.info->fprintf_func) (mra.info->stream, ".%c",
                                  shift_size_table[osize]);
   }

 /* Ship out the operands.  */
 for (o = 0; o < n_operands; ++o)
   {
     if (operands[o])
       opr_emit_disassembly (operands[o], mra.info);
     free (operands[o]);
   }

 return n_bytes;
}