/* ia64-opc.c -- Functions to access the compacted opcode table
  Copyright (C) 1999-2024 Free Software Foundation, Inc.
  Written by Bob Manson of Cygnus Solutions, <[email protected]>

  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 "libiberty.h"
#include "ia64-asmtab.h"
#include "ia64-asmtab.c"

static void get_opc_prefix (const char **, char *);
static short int find_string_ent (const char *);
static short int find_main_ent (short int);
static short int find_completer (short int, short int, const char *);
static ia64_insn apply_completer (ia64_insn, int);
static int extract_op_bits (int, int, int);
static int extract_op (int, int *, unsigned int *);
static int opcode_verify (ia64_insn, int, enum ia64_insn_type);
static int locate_opcode_ent (ia64_insn, enum ia64_insn_type);
static struct ia64_opcode *make_ia64_opcode
 (ia64_insn, const char *, int, int);
static struct ia64_opcode *ia64_find_matching_opcode
 (const char *, short int);

const struct ia64_templ_desc ia64_templ_desc[16] =
 {
   { 0, { IA64_UNIT_M, IA64_UNIT_I, IA64_UNIT_I }, "MII" },    /* 0 */
   { 2, { IA64_UNIT_M, IA64_UNIT_I, IA64_UNIT_I }, "MII" },
   { 0, { IA64_UNIT_M, IA64_UNIT_L, IA64_UNIT_X }, "MLX" },
   { 0, { 0, },                                    "-3-" },
   { 0, { IA64_UNIT_M, IA64_UNIT_M, IA64_UNIT_I }, "MMI" },    /* 4 */
   { 1, { IA64_UNIT_M, IA64_UNIT_M, IA64_UNIT_I }, "MMI" },
   { 0, { IA64_UNIT_M, IA64_UNIT_F, IA64_UNIT_I }, "MFI" },
   { 0, { IA64_UNIT_M, IA64_UNIT_M, IA64_UNIT_F }, "MMF" },
   { 0, { IA64_UNIT_M, IA64_UNIT_I, IA64_UNIT_B }, "MIB" },    /* 8 */
   { 0, { IA64_UNIT_M, IA64_UNIT_B, IA64_UNIT_B }, "MBB" },
   { 0, { 0, },                                    "-a-" },
   { 0, { IA64_UNIT_B, IA64_UNIT_B, IA64_UNIT_B }, "BBB" },
   { 0, { IA64_UNIT_M, IA64_UNIT_M, IA64_UNIT_B }, "MMB" },    /* c */
   { 0, { 0, },                                    "-d-" },
   { 0, { IA64_UNIT_M, IA64_UNIT_F, IA64_UNIT_B }, "MFB" },
   { 0, { 0, },                                    "-f-" },
 };


/* Copy the prefix contained in *PTR (up to a '.' or a NUL) to DEST.
  PTR will be adjusted to point to the start of the next portion
  of the opcode, or at the NUL character. */

static void
get_opc_prefix (const char **ptr, char *dest)
{
 char *c = strchr (*ptr, '.');
 if (c != NULL)
   {
     memcpy (dest, *ptr, c - *ptr);
     dest[c - *ptr] = '\0';
     *ptr = c + 1;
   }
 else
   {
     int l = strlen (*ptr);
     memcpy (dest, *ptr, l);
     dest[l] = '\0';
     *ptr += l;
   }
}

/* Find the index of the entry in the string table corresponding to
  STR; return -1 if one does not exist. */

static short
find_string_ent (const char *str)
{
 short start = 0;
 short end = sizeof (ia64_strings) / sizeof (const char *);
 short i = (start + end) / 2;

 if (strcmp (str, ia64_strings[end - 1]) > 0)
   {
     return -1;
   }
 while (start <= end)
   {
     int c = strcmp (str, ia64_strings[i]);
     if (c < 0)
       {
         end = i - 1;
       }
     else if (c == 0)
       {
         return i;
       }
     else
       {
         start = i + 1;
       }
     i = (start + end) / 2;
   }
 return -1;
}

/* Find the opcode in the main opcode table whose name is STRINGINDEX, or
  return -1 if one does not exist. */

static short
find_main_ent (short nameindex)
{
 short start = 0;
 short end = ARRAY_SIZE (main_table);
 short i = (start + end) / 2;

 if (nameindex < main_table[0].name_index
     || nameindex > main_table[end - 1].name_index)
   {
     return -1;
   }
 while (start <= end)
   {
     if (nameindex < main_table[i].name_index)
       {
         end = i - 1;
       }
     else if (nameindex == main_table[i].name_index)
       {
         while (i > 0 && main_table[i - 1].name_index == nameindex)
           {
             i--;
           }
         return i;
       }
     else
       {
         start = i + 1;
       }
     i = (start + end) / 2;
   }
 return -1;
}

/* Find the index of the entry in the completer table that is part of
  MAIN_ENT (starting from PREV_COMPLETER) that matches NAME, or
  return -1 if one does not exist. */

static short
find_completer (short main_ent, short prev_completer, const char *name)
{
 short name_index = find_string_ent (name);

 if (name_index < 0)
   {
     return -1;
   }

 if (prev_completer == -1)
   {
     prev_completer = main_table[main_ent].completers;
   }
 else
   {
     prev_completer = completer_table[prev_completer].subentries;
   }

 while (prev_completer != -1)
   {
     if (completer_table[prev_completer].name_index == name_index)
       {
         return prev_completer;
       }
     prev_completer = completer_table[prev_completer].alternative;
   }
 return -1;
}

/* Apply the completer referred to by COMPLETER_INDEX to OPCODE, and
  return the result. */

static ia64_insn
apply_completer (ia64_insn opcode, int completer_index)
{
 ia64_insn mask = completer_table[completer_index].mask;
 ia64_insn bits = completer_table[completer_index].bits;
 int shiftamt = (completer_table[completer_index].offset & 63);

 mask = mask << shiftamt;
 bits = bits << shiftamt;
 opcode = (opcode & ~mask) | bits;
 return opcode;
}

/* Extract BITS number of bits starting from OP_POINTER + BITOFFSET in
  the dis_table array, and return its value.  (BITOFFSET is numbered
  starting from MSB to LSB, so a BITOFFSET of 0 indicates the MSB of the
  first byte in OP_POINTER.) */

static int
extract_op_bits (int op_pointer, int bitoffset, int bits)
{
 int res = 0;

 op_pointer += (bitoffset / 8);

 if (bitoffset % 8)
   {
     unsigned int op = dis_table[op_pointer++];
     int numb = 8 - (bitoffset % 8);
     int mask = (1 << numb) - 1;
     int bata = (bits < numb) ? bits : numb;
     int delta = numb - bata;

     res = (res << bata) | ((op & mask) >> delta);
     bitoffset += bata;
     bits -= bata;
   }
 while (bits >= 8)
   {
     res = (res << 8) | (dis_table[op_pointer++] & 255);
     bits -= 8;
   }
 if (bits > 0)
   {
     unsigned int op = (dis_table[op_pointer++] & 255);
     res = (res << bits) | (op >> (8 - bits));
   }
 return res;
}

/* Examine the state machine entry at OP_POINTER in the dis_table
  array, and extract its values into OPVAL and OP.  The length of the
  state entry in bits is returned. */

static int
extract_op (int op_pointer, int *opval, unsigned int *op)
{
 int oplen = 5;

 *op = dis_table[op_pointer];

 if ((*op) & 0x40)
   {
     opval[0] = extract_op_bits (op_pointer, oplen, 5);
     oplen += 5;
   }
 switch ((*op) & 0x30)
   {
   case 0x10:
     {
       opval[1] = extract_op_bits (op_pointer, oplen, 8);
       oplen += 8;
       opval[1] += op_pointer;
       break;
     }
   case 0x20:
     {
       opval[1] = extract_op_bits (op_pointer, oplen, 16);
       if (! (opval[1] & 32768))
         {
           opval[1] += op_pointer;
         }
       oplen += 16;
       break;
     }
   case 0x30:
     {
       oplen--;
       opval[2] = extract_op_bits (op_pointer, oplen, 12);
       oplen += 12;
       opval[2] |= 32768;
       break;
     }
   }
 if (((*op) & 0x08) && (((*op) & 0x30) != 0x30))
   {
     opval[2] = extract_op_bits (op_pointer, oplen, 16);
     oplen += 16;
     if (! (opval[2] & 32768))
       {
         opval[2] += op_pointer;
       }
   }
 return oplen;
}

/* Returns a non-zero value if the opcode in the main_table list at
  PLACE matches OPCODE and is of type TYPE. */

static int
opcode_verify (ia64_insn opcode, int place, enum ia64_insn_type type)
{
 if (main_table[place].opcode_type != type)
   {
     return 0;
   }
 if (main_table[place].flags
     & (IA64_OPCODE_F2_EQ_F3 | IA64_OPCODE_LEN_EQ_64MCNT))
   {
     const struct ia64_operand *o1, *o2;
     ia64_insn f2, f3;

     if (main_table[place].flags & IA64_OPCODE_F2_EQ_F3)
       {
         o1 = elf64_ia64_operands + IA64_OPND_F2;
         o2 = elf64_ia64_operands + IA64_OPND_F3;
         (*o1->extract) (o1, opcode, &f2);
         (*o2->extract) (o2, opcode, &f3);
         if (f2 != f3)
           return 0;
       }
     else
       {
         ia64_insn len, count;

         /* length must equal 64-count: */
         o1 = elf64_ia64_operands + IA64_OPND_LEN6;
         o2 = elf64_ia64_operands + main_table[place].operands[2];
         (*o1->extract) (o1, opcode, &len);
         (*o2->extract) (o2, opcode, &count);
         if (len != 64 - count)
           return 0;
       }
   }
 return 1;
}

/* Find an instruction entry in the ia64_dis_names array that matches
  opcode OPCODE and is of type TYPE.  Returns either a positive index
  into the array, or a negative value if an entry for OPCODE could
  not be found.  Checks all matches and returns the one with the highest
  priority. */

static int
locate_opcode_ent (ia64_insn opcode, enum ia64_insn_type type)
{
 int currtest[41];
 int bitpos[41];
 int op_ptr[41];
 int currstatenum = 0;
 short found_disent = -1;
 short found_priority = -1;

 currtest[currstatenum] = 0;
 op_ptr[currstatenum] = 0;
 bitpos[currstatenum] = 40;

 while (1)
   {
     int op_pointer = op_ptr[currstatenum];
     unsigned int op;
     int currbitnum = bitpos[currstatenum];
     int oplen;
     int opval[3] = {0};
     int next_op;
     int currbit;

     oplen = extract_op (op_pointer, opval, &op);

     bitpos[currstatenum] = currbitnum;

     /* Skip opval[0] bits in the instruction.  */
     if (op & 0x40)
       {
         currbitnum -= opval[0];
       }

     if (currbitnum < 0)
       currbitnum = 0;

     /* The value of the current bit being tested.  */
     currbit = opcode & (((ia64_insn) 1) << currbitnum) ? 1 : 0;
     next_op = -1;

     /* We always perform the tests specified in the current state in
        a particular order, falling through to the next test if the
        previous one failed. */
     switch (currtest[currstatenum])
       {
       case 0:
         currtest[currstatenum]++;
         if (currbit == 0 && (op & 0x80))
           {
             /* Check for a zero bit.  If this test solely checks for
                a zero bit, we can check for up to 8 consecutive zero
                bits (the number to check is specified by the lower 3
                bits in the state code.)

                If the state instruction matches, we go to the very
                next state instruction; otherwise, try the next test. */

             if ((op & 0xf8) == 0x80)
               {
                 int count = op & 0x7;
                 int x;

                 for (x = 0; x <= count; x++)
                   {
                     int i =
                       opcode & (((ia64_insn) 1) << (currbitnum - x)) ? 1 : 0;
                     if (i)
                       {
                         break;
                       }
                   }
                 if (x > count)
                   {
                     next_op = op_pointer + ((oplen + 7) / 8);
                     currbitnum -= count;
                     break;
                   }
               }
             else if (! currbit)
               {
                 next_op = op_pointer + ((oplen + 7) / 8);
                 break;
               }
           }
         /* FALLTHROUGH */
       case 1:
         /* If the bit in the instruction is one, go to the state
            instruction specified by opval[1]. */
         currtest[currstatenum]++;
         if (currbit && (op & 0x30) != 0 && ((op & 0x30) != 0x30))
           {
             next_op = opval[1];
             break;
           }
         /* FALLTHROUGH */
       case 2:
         /* Don't care.  Skip the current bit and go to the state
            instruction specified by opval[2].

            An encoding of 0x30 is special; this means that a 12-bit
            offset into the ia64_dis_names[] array is specified.  */
         currtest[currstatenum]++;
         if ((op & 0x08) || ((op & 0x30) == 0x30))
           {
             next_op = opval[2];
             break;
           }
       }

     /* If bit 15 is set in the address of the next state, an offset
        in the ia64_dis_names array was specified instead.  We then
        check to see if an entry in the list of opcodes matches the
        opcode we were given; if so, we have succeeded.  */

     if ((next_op >= 0) && (next_op & 32768))
       {
         short disent = next_op & 32767;
         short priority = -1;

         if (next_op > 65535)
           {
             return -1;
           }

         /* Run through the list of opcodes to check, trying to find
            one that matches.  */
         while (disent >= 0)
           {
             int place = ia64_dis_names[disent].insn_index;

             priority = ia64_dis_names[disent].priority;

             if (opcode_verify (opcode, place, type)
                 && priority > found_priority)
               {
                 break;
               }
             if (ia64_dis_names[disent].next_flag)
               {
                 disent++;
               }
             else
               {
                 disent = -1;
               }
           }

         if (disent >= 0)
           {
             found_disent = disent;
             found_priority = priority;
           }
         /* Try the next test in this state, regardless of whether a match
            was found. */
         next_op = -2;
       }

     /* next_op == -1 is "back up to the previous state".
        next_op == -2 is "stay in this state and try the next test".
        Otherwise, transition to the state indicated by next_op. */

     if (next_op == -1)
       {
         currstatenum--;
         if (currstatenum < 0)
           {
             return found_disent;
           }
       }
     else if (next_op >= 0)
       {
         currstatenum++;
         bitpos[currstatenum] = currbitnum - 1;
         op_ptr[currstatenum] = next_op;
         currtest[currstatenum] = 0;
       }
   }
}

/* Construct an ia64_opcode entry based on OPCODE, NAME and PLACE. */

static struct ia64_opcode *
make_ia64_opcode (ia64_insn opcode, const char *name, int place, int depind)
{
 struct ia64_opcode *res =
   (struct ia64_opcode *) xmalloc (sizeof (struct ia64_opcode));
 res->name = xstrdup (name);
 res->type = main_table[place].opcode_type;
 res->num_outputs = main_table[place].num_outputs;
 res->opcode = opcode;
 res->mask = main_table[place].mask;
 res->operands[0] = main_table[place].operands[0];
 res->operands[1] = main_table[place].operands[1];
 res->operands[2] = main_table[place].operands[2];
 res->operands[3] = main_table[place].operands[3];
 res->operands[4] = main_table[place].operands[4];
 res->flags = main_table[place].flags;
 res->ent_index = place;
 res->dependencies = &op_dependencies[depind];
 return res;
}

/* Determine the ia64_opcode entry for the opcode specified by INSN
  and TYPE.  If a valid entry is not found, return NULL. */
struct ia64_opcode *
ia64_dis_opcode (ia64_insn insn, enum ia64_insn_type type)
{
 int disent = locate_opcode_ent (insn, type);

 if (disent < 0)
   {
     return NULL;
   }
 else
   {
     unsigned int cb = ia64_dis_names[disent].completer_index;
     static char name[128];
     int place = ia64_dis_names[disent].insn_index;
     int ci = main_table[place].completers;
     ia64_insn tinsn = main_table[place].opcode;

     strcpy (name, ia64_strings [main_table[place].name_index]);

     while (cb)
       {
         if (cb & 1)
           {
             int cname = completer_table[ci].name_index;

             tinsn = apply_completer (tinsn, ci);

             if (ia64_strings[cname][0] != '\0')
               {
                 strcat (name, ".");
                 strcat (name, ia64_strings[cname]);
               }
             if (cb != 1)
               {
                 ci = completer_table[ci].subentries;
               }
           }
         else
           {
             ci = completer_table[ci].alternative;
           }
         if (ci < 0)
           {
             abort ();
           }
         cb = cb >> 1;
       }
     if (tinsn != (insn & main_table[place].mask))
       {
         abort ();
       }
     return make_ia64_opcode (insn, name, place,
                              completer_table[ci].dependencies);
   }
}

/* Search the main_opcode table starting from PLACE for an opcode that
  matches NAME.  Return NULL if one is not found. */

static struct ia64_opcode *
ia64_find_matching_opcode (const char *name, short place)
{
 char op[129];
 const char *suffix;
 short name_index;

 if ((unsigned) place >= ARRAY_SIZE (main_table))
   return NULL;

 if (strlen (name) > 128)
   {
     return NULL;
   }
 suffix = name;
 get_opc_prefix (&suffix, op);
 name_index = find_string_ent (op);
 if (name_index < 0)
   {
     return NULL;
   }

 while (main_table[place].name_index == name_index)
   {
     const char *curr_suffix = suffix;
     ia64_insn curr_insn = main_table[place].opcode;
     short completer = -1;

     do {
       if (suffix[0] == '\0')
         {
           completer = find_completer (place, completer, suffix);
         }
       else
         {
           get_opc_prefix (&curr_suffix, op);
           completer = find_completer (place, completer, op);
         }
       if (completer != -1)
         {
           curr_insn = apply_completer (curr_insn, completer);
         }
     } while (completer != -1 && curr_suffix[0] != '\0');

     if (completer != -1 && curr_suffix[0] == '\0'
         && completer_table[completer].terminal_completer)
       {
         int depind = completer_table[completer].dependencies;
         return make_ia64_opcode (curr_insn, name, place, depind);
       }
     else
       {
         place++;
       }
   }
 return NULL;
}

/* Find the next opcode after PREV_ENT that matches PREV_ENT, or return NULL
  if one does not exist.

  It is the caller's responsibility to invoke ia64_free_opcode () to
  release any resources used by the returned entry. */

struct ia64_opcode *
ia64_find_next_opcode (struct ia64_opcode *prev_ent)
{
 return ia64_find_matching_opcode (prev_ent->name,
                                   prev_ent->ent_index + 1);
}

/* Find the first opcode that matches NAME, or return NULL if it does
  not exist.

  It is the caller's responsibility to invoke ia64_free_opcode () to
  release any resources used by the returned entry. */

struct ia64_opcode *
ia64_find_opcode (const char *name)
{
 char op[129];
 const char *suffix;
 short place;
 short name_index;

 if (strlen (name) > 128)
   {
     return NULL;
   }
 suffix = name;
 get_opc_prefix (&suffix, op);
 name_index = find_string_ent (op);
 if (name_index < 0)
   {
     return NULL;
   }

 place = find_main_ent (name_index);

 if (place < 0)
   {
     return NULL;
   }
 return ia64_find_matching_opcode (name, place);
}

/* Free any resources used by ENT. */
void
ia64_free_opcode (struct ia64_opcode *ent)
{
 free ((void *)ent->name);
 free (ent);
}

const struct ia64_dependency *
ia64_find_dependency (int dep_index)
{
 dep_index = DEP(dep_index);

 if (dep_index < 0
     || dep_index >= (int) ARRAY_SIZE (dependencies))
   return NULL;

 return &dependencies[dep_index];
}