/* Helper routines for disassembler for x86/x86-64.
   Copyright (C) 2007, 2008 Red Hat, Inc.
   This file is part of Red Hat elfutils.
   Written by Ulrich Drepper <drepper@redhat.com>, 2007.

   Red Hat elfutils 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; version 2 of the License.

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

   Red Hat elfutils is an included package of the Open Invention Network.
   An included package of the Open Invention Network is a package for which
   Open Invention Network licensees cross-license their patents.  No patent
   license is granted, either expressly or impliedly, by designation as an
   included package.  Should you wish to participate in the Open Invention
   Network licensing program, please visit www.openinventionnetwork.com
   <http://www.openinventionnetwork.com>.  */

#include <inttypes.h>
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <libasm.h>

struct instr_enc
{
  /* The mnemonic.  Especially encoded for the optimized table.  */
  unsigned int mnemonic : MNEMONIC_BITS;

  /* The rep/repe prefixes.  */
  unsigned int rep : 1;
  unsigned int repe : 1;

  /* Mnemonic suffix.  */
  unsigned int suffix : SUFFIX_BITS;

  /* Nonzero if the instruction uses modr/m.  */
  unsigned int modrm : 1;

  /* 1st parameter.  */
  unsigned int fct1 : FCT1_BITS;
#ifdef STR1_BITS
  unsigned int str1 : STR1_BITS;
#endif
  unsigned int off1_1 : OFF1_1_BITS;
  unsigned int off1_2 : OFF1_2_BITS;
  unsigned int off1_3 : OFF1_3_BITS;

  /* 2nd parameter.  */
  unsigned int fct2 : FCT2_BITS;
#ifdef STR2_BITS
  unsigned int str2 : STR2_BITS;
#endif
  unsigned int off2_1 : OFF2_1_BITS;
  unsigned int off2_2 : OFF2_2_BITS;
  unsigned int off2_3 : OFF2_3_BITS;

  /* 3rd parameter.  */
  unsigned int fct3 : FCT3_BITS;
#ifdef STR3_BITS
  unsigned int str3 : STR3_BITS;
#endif
  unsigned int off3_1 : OFF3_1_BITS;
#ifdef OFF3_2_BITS
  unsigned int off3_2 : OFF3_2_BITS;
#endif
#ifdef OFF3_3_BITS
  unsigned int off3_3 : OFF3_3_BITS;
#endif
};


typedef int (*opfct_t) (struct output_data *);


static int
data_prefix (struct output_data *d)
{
  char ch = '\0';
  if (*d->prefixes & has_cs)
    {
      ch = 'c';
      *d->prefixes &= ~has_cs;
    }
  else if (*d->prefixes & has_ds)
    {
      ch = 'd';
      *d->prefixes &= ~has_ds;
    }
  else if (*d->prefixes & has_es)
    {
      ch = 'e';
      *d->prefixes &= ~has_es;
    }
  else if (*d->prefixes & has_fs)
    {
      ch = 'f';
      *d->prefixes &= ~has_fs;
    }
  else if (*d->prefixes & has_gs)
    {
      ch = 'g';
      *d->prefixes &= ~has_gs;
    }
  else if (*d->prefixes & has_ss)
    {
      ch = 's';
      *d->prefixes &= ~has_ss;
    }
  else
    return 0;

  if (*d->bufcntp + 4 > d->bufsize)
    return *d->bufcntp + 4 - d->bufsize;

  d->bufp[(*d->bufcntp)++] = '%';
  d->bufp[(*d->bufcntp)++] = ch;
  d->bufp[(*d->bufcntp)++] = 's';
  d->bufp[(*d->bufcntp)++] = ':';

  return 0;
}

#ifdef X86_64
static const char hiregs[8][4] =
  {
    "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"
  };
static const char aregs[8][4] =
  {
    "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi"
  };
static const char dregs[8][4] =
  {
    "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi"
  };
#else
static const char aregs[8][4] =
  {
    "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi"
  };
# define dregs aregs
#endif

static int
general_mod$r_m (struct output_data *d)
{
  int r = data_prefix (d);
  if (r != 0)
    return r;

  int prefixes = *d->prefixes;
  const uint8_t *data = &d->data[d->opoff1 / 8];
  char *bufp = d->bufp;
  size_t *bufcntp = d->bufcntp;
  size_t bufsize = d->bufsize;

  uint_fast8_t modrm = data[0];
#ifndef X86_64
  if (unlikely ((prefixes & has_addr16) != 0))
    {
      int16_t disp = 0;
      bool nodisp = false;

      if ((modrm & 0xc7) == 6 || (modrm & 0xc0) == 0x80)
	/* 16 bit displacement.  */
	disp = read_2sbyte_unaligned (&data[1]);
      else if ((modrm & 0xc0) == 0x40)
	/* 8 bit displacement.  */
	disp = *(const int8_t *) &data[1];
      else if ((modrm & 0xc0) == 0)
	nodisp = true;

      char tmpbuf[sizeof ("-0x1234(%rr,%rr)")];
      int n;
      if ((modrm & 0xc7) == 6)
	n = snprintf (tmpbuf, sizeof (tmpbuf), "0x%" PRIx16, disp);
      else
	{
	  n = 0;
	  if (!nodisp)
	    n = snprintf (tmpbuf, sizeof (tmpbuf), "%s0x%" PRIx16,
			  disp < 0 ? "-" : "", disp < 0 ? -disp : disp);

	  if ((modrm & 0x4) == 0)
	    n += snprintf (tmpbuf + n, sizeof (tmpbuf) - n, "(%%b%c,%%%ci)",
			   "xp"[(modrm >> 1) & 1], "sd"[modrm & 1]);
	  else
	    n += snprintf (tmpbuf + n, sizeof (tmpbuf) - n, "(%%%s)",
			   ((const char [4][3]) { "si", "di", "bp", "bx" })[modrm & 3]);
	}

      if (*bufcntp + n + 1 > bufsize)
	return *bufcntp + n + 1 - bufsize;

      memcpy (&bufp[*bufcntp], tmpbuf, n + 1);
      *bufcntp += n;
    }
  else
#endif
    {
      if ((modrm & 7) != 4)
	{
	  int32_t disp = 0;
	  bool nodisp = false;

	  if ((modrm & 0xc7) == 5 || (modrm & 0xc0) == 0x80)
	    /* 32 bit displacement.  */
	    disp = read_4sbyte_unaligned (&data[1]);
	  else if ((modrm & 0xc0) == 0x40)
	    /* 8 bit displacement.  */
	    disp = *(const int8_t *) &data[1];
	  else if ((modrm & 0xc0) == 0)
	    nodisp = true;

	  char tmpbuf[sizeof ("-0x12345678(%rrrr)")];
	  int n;
	  if (nodisp)
	    {
	      n = snprintf (tmpbuf, sizeof (tmpbuf), "(%%%s)",
#ifdef X86_64
			    (prefixes & has_rex_b) ? hiregs[modrm & 7] :
#endif
			    aregs[modrm & 7]);
#ifdef X86_64
	      if (prefixes & has_addr16)
		{
		  if (prefixes & has_rex_b)
		    tmpbuf[n++] = 'd';
		  else
		    tmpbuf[2] = 'e';
		}
#endif
	    }
	  else if ((modrm & 0xc7) != 5)
	    {
	      int p;
	      n = snprintf (tmpbuf, sizeof (tmpbuf), "%s0x%" PRIx32 "(%%%n%s)",
			    disp < 0 ? "-" : "", disp < 0 ? -disp : disp, &p,
#ifdef X86_64
			    (prefixes & has_rex_b) ? hiregs[modrm & 7] :
#endif
			    aregs[modrm & 7]);
#ifdef X86_64
	      if (prefixes & has_addr16)
		{
		  if (prefixes & has_rex_b)
		    tmpbuf[n++] = 'd';
		  else
		    tmpbuf[p] = 'e';
		}
#endif
	    }
	  else
	    {
#ifdef X86_64
	      n = snprintf (tmpbuf, sizeof (tmpbuf), "%s0x%" PRIx32 "(%%rip)",
			    disp < 0 ? "-" : "", disp < 0 ? -disp : disp);

	      d->symaddr_use = addr_rel_always;
	      d->symaddr = disp;
#else
	      n = snprintf (tmpbuf, sizeof (tmpbuf), "0x%" PRIx32, disp);
#endif
	    }

	  if (*bufcntp + n + 1 > bufsize)
	    return *bufcntp + n + 1 - bufsize;

	  memcpy (&bufp[*bufcntp], tmpbuf, n + 1);
	  *bufcntp += n;
	}
      else
	{
	  /* SIB */
	  uint_fast8_t sib = data[1];
	  int32_t disp = 0;
	  bool nodisp = false;

	  if ((modrm & 0xc7) == 5 || (modrm & 0xc0) == 0x80
	      || ((modrm & 0xc7) == 0x4 && (sib & 0x7) == 0x5))
	    /* 32 bit displacement.  */
	    disp = read_4sbyte_unaligned (&data[2]);
	  else if ((modrm & 0xc0) == 0x40)
	    /* 8 bit displacement.  */
	    disp = *(const int8_t *) &data[2];
	  else
	    nodisp = true;

	  char tmpbuf[sizeof ("-0x12345678(%rrrr,%rrrr,N)")];
	  char *cp = tmpbuf;
	  int n;
	  if ((modrm & 0xc0) != 0 || (sib & 0x3f) != 0x25
#ifdef X86_64
	      || (prefixes & has_rex_x) != 0
#endif
	      )
	    {
	      if (!nodisp)
		{
		  n = snprintf (cp, sizeof (tmpbuf), "%s0x%" PRIx32,
				disp < 0 ? "-" : "", disp < 0 ? -disp : disp);
		  cp += n;
		}

	      *cp++ = '(';

	      if ((modrm & 0xc7) != 0x4 || (sib & 0x7) != 0x5)
		{
		  *cp++ = '%';
		  cp = stpcpy (cp,
#ifdef X86_64
			       (prefixes & has_rex_b) ? hiregs[sib & 7] :
			       (prefixes & has_addr16) ? dregs[sib & 7] :
#endif
			       aregs[sib & 7]);
#ifdef X86_64
		  if ((prefixes & (has_rex_b | has_addr16))
		      == (has_rex_b | has_addr16))
		    *cp++ = 'd';
#endif
		}

	      if ((sib & 0x38) != 0x20
#ifdef X86_64
		  || (prefixes & has_rex_x) != 0
#endif
		  )
		{
		  *cp++ = ',';
		  *cp++ = '%';
		  cp = stpcpy (cp,
#ifdef X86_64
			       (prefixes & has_rex_x)
			       ? hiregs[(sib >> 3) & 7] :
			       (prefixes & has_addr16)
			       ? dregs[(sib >> 3) & 7] :
#endif
			       aregs[(sib >> 3) & 7]);
#ifdef X86_64
		  if ((prefixes & (has_rex_b | has_addr16))
		      == (has_rex_b | has_addr16))
		    *cp++ = 'd';
#endif

		  *cp++ = ',';
		  *cp++ = '0' + (1 << (sib >> 6));
		}

	      *cp++ = ')';
	    }
	  else
	    {
	      assert (! nodisp);
#ifdef X86_64
	      if ((prefixes & has_addr16) == 0)
		n = snprintf (cp, sizeof (tmpbuf), "0x%" PRIx64,
			      (int64_t) disp);
	      else
#endif
		n = snprintf (cp, sizeof (tmpbuf), "0x%" PRIx32, disp);
	      cp += n;
	    }

	  if (*bufcntp + (cp - tmpbuf) > bufsize)
	    return *bufcntp + (cp - tmpbuf) - bufsize;

	  memcpy (&bufp[*bufcntp], tmpbuf, cp - tmpbuf);
	  *bufcntp += cp - tmpbuf;
	}
    }
  return 0;
}


static int
FCT_MOD$R_M (struct output_data *d)
{
  assert (d->opoff1 % 8 == 0);
  uint_fast8_t modrm = d->data[d->opoff1 / 8];
  if ((modrm & 0xc0) == 0xc0)
    {
      assert (d->opoff1 / 8 == d->opoff2 / 8);
      assert (d->opoff2 % 8 == 5);
      //uint_fast8_t byte = d->data[d->opoff2 / 8] & 7;
      uint_fast8_t byte = modrm & 7;

      size_t *bufcntp = d->bufcntp;
      char *buf = d->bufp + *bufcntp;
      size_t avail = d->bufsize - *bufcntp;
      int needed;
      if (*d->prefixes & (has_rep | has_repne))
	needed = snprintf (buf, avail, "%%%s", dregs[byte]);
      else
	needed = snprintf (buf, avail, "%%mm%" PRIxFAST8, byte);
      if ((size_t) needed > avail)
	return needed - avail;
      *bufcntp += needed;
      return 0;
    }

  return general_mod$r_m (d);
}


static int
FCT_Mod$R_m (struct output_data *d)
{
  assert (d->opoff1 % 8 == 0);
  uint_fast8_t modrm = d->data[d->opoff1 / 8];
  if ((modrm & 0xc0) == 0xc0)
    {
      assert (d->opoff1 / 8 == d->opoff2 / 8);
      assert (d->opoff2 % 8 == 5);
      //uint_fast8_t byte = data[opoff2 / 8] & 7;
      uint_fast8_t byte = modrm & 7;

      size_t *bufcntp = d->bufcntp;
      size_t avail = d->bufsize - *bufcntp;
      int needed = snprintf (&d->bufp[*bufcntp], avail, "%%xmm%" PRIxFAST8,
			     byte);
      if ((size_t) needed > avail)
	return needed - avail;
      *d->bufcntp += needed;
      return 0;
    }

  return general_mod$r_m (d);
}

static int
generic_abs (struct output_data *d, const char *absstring
#ifdef X86_64
	     , int abslen
#else
# define abslen 4
#endif
	     )
{
  int r = data_prefix (d);
  if (r != 0)
    return r;

  assert (d->opoff1 % 8 == 0);
  assert (d->opoff1 / 8 == 1);
  if (*d->param_start + abslen > d->end)
    return -1;
  *d->param_start += abslen;
#ifndef X86_64
  uint32_t absval;
# define ABSPRIFMT PRIx32
#else
  uint64_t absval;
# define ABSPRIFMT PRIx64
  if (abslen == 8)
    absval = read_8ubyte_unaligned (&d->data[1]);
  else
#endif
    absval = read_4ubyte_unaligned (&d->data[1]);
  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  int needed = snprintf (&d->bufp[*bufcntp], avail, "%s0x%" ABSPRIFMT,
			 absstring, absval);
  if ((size_t) needed > avail)
    return needed - avail;
  *bufcntp += needed;
  return 0;
}


static int
FCT_absval (struct output_data *d)
{
  return generic_abs (d, "$"
#ifdef X86_64
		      , 4
#endif
		      );
}

static int
FCT_abs (struct output_data *d)
{
  return generic_abs (d, ""
#ifdef X86_64
		      , 8
#endif
		      );
}

static int
FCT_ax (struct output_data *d)
{
  int is_16bit = (*d->prefixes & has_data16) != 0;

  size_t *bufcntp = d->bufcntp;
  char *bufp = d->bufp;
  size_t bufsize = d->bufsize;

  if (*bufcntp + 4 - is_16bit > bufsize)
    return *bufcntp + 4 - is_16bit - bufsize;

  bufp[(*bufcntp)++] = '%';
  if (! is_16bit)
    bufp[(*bufcntp)++] = (
#ifdef X86_64
			  (*d->prefixes & has_rex_w) ? 'r' :
#endif
			  'e');
  bufp[(*bufcntp)++] = 'a';
  bufp[(*bufcntp)++] = 'x';

  return 0;
}


static int
FCT_ax$w (struct output_data *d)
{
  if ((d->data[d->opoff2 / 8] & (1 << (7 - (d->opoff2 & 7)))) != 0)
    return FCT_ax (d);

  size_t *bufcntp = d->bufcntp;
  char *bufp = d->bufp;
  size_t bufsize = d->bufsize;

  if (*bufcntp + 3 > bufsize)
    return *bufcntp + 3 - bufsize;

  bufp[(*bufcntp)++] = '%';
  bufp[(*bufcntp)++] = 'a';
  bufp[(*bufcntp)++] = 'l';

  return 0;
}


static int
__attribute__ ((noinline))
FCT_crdb (struct output_data *d, const char *regstr)
{
  if (*d->prefixes & has_data16)
    return -1;

  size_t *bufcntp = d->bufcntp;

  // XXX If this assert is true, use absolute offset below
  assert (d->opoff1 / 8 == 2);
  assert (d->opoff1 % 8 == 2);
  size_t avail = d->bufsize - *bufcntp;
  int needed = snprintf (&d->bufp[*bufcntp], avail, "%%%s%" PRIx32,
			 regstr, (uint32_t) (d->data[d->opoff1 / 8] >> 3) & 7);
  if ((size_t) needed > avail)
    return needed - avail;
  *bufcntp += needed;
  return 0;
}


static int
FCT_ccc (struct output_data *d)
{
  return FCT_crdb (d, "cr");
}


static int
FCT_ddd (struct output_data *d)
{
  return FCT_crdb (d, "db");
}


static int
FCT_disp8 (struct output_data *d)
{
  assert (d->opoff1 % 8 == 0);
  if (*d->param_start >= d->end)
    return -1;
  int32_t offset = *(const int8_t *) (*d->param_start)++;

  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  int needed = snprintf (&d->bufp[*bufcntp], avail, "0x%" PRIx32,
			 (uint32_t) (d->addr + (*d->param_start - d->data)
				     + offset));
  if ((size_t) needed > avail)
    return needed - avail;
  *bufcntp += needed;
  return 0;
}


static int
__attribute__ ((noinline))
FCT_ds_xx (struct output_data *d, const char *reg)
{
  int prefix = *d->prefixes & SEGMENT_PREFIXES;

  if (prefix == 0)
    *d->prefixes |= prefix = has_ds;
  /* Make sure only one bit is set.  */
  else if ((prefix - 1) & prefix)
    return -1;

  int r = data_prefix (d);

  assert ((*d->prefixes & prefix) == 0);

  if (r != 0)
    return r;

  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  int needed = snprintf (&d->bufp[*bufcntp], avail, "(%%%s%s)",
#ifdef X86_64
			 *d->prefixes & idx_addr16 ? "e" : "r",
#else
			 *d->prefixes & idx_addr16 ? "" : "e",
#endif
			 reg);
  if ((size_t) needed > avail)
    return (size_t) needed - avail;
  *bufcntp += needed;

  return 0;
}


static int
FCT_ds_bx (struct output_data *d)
{
  return FCT_ds_xx (d, "bx");
}


static int
FCT_ds_si (struct output_data *d)
{
  return FCT_ds_xx (d, "si");
}


static int
FCT_dx (struct output_data *d)
{
  size_t *bufcntp = d->bufcntp;

  if (*bufcntp + 7 > d->bufsize)
    return *bufcntp + 7 - d->bufsize;

  memcpy (&d->bufp[*bufcntp], "(%dx)", 5);
  *bufcntp += 5;

  return 0;
}


static int
FCT_es_di (struct output_data *d)
{
  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  int needed = snprintf (&d->bufp[*bufcntp], avail, "%%es:(%%%sdi)",
#ifdef X86_64
			 *d->prefixes & idx_addr16 ? "e" : "r"
#else
			 *d->prefixes & idx_addr16 ? "" : "e"
#endif
			 );
  if ((size_t) needed > avail)
    return (size_t) needed - avail;
  *bufcntp += needed;

  return 0;
}


static int
FCT_imm (struct output_data *d)
{
  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  int needed;
  if (*d->prefixes & has_data16)
    {
      if (*d->param_start + 2 > d->end)
	return -1;
      uint16_t word = read_2ubyte_unaligned_inc (*d->param_start);
      needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx16, word);
    }
  else
    {
      if (*d->param_start + 4 > d->end)
	return -1;
      int32_t word = read_4sbyte_unaligned_inc (*d->param_start);
#ifdef X86_64
      if (*d->prefixes & has_rex_w)
	needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx64,
			   (int64_t) word);
      else
#endif
	needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx32, word);
    }
  if ((size_t) needed > avail)
    return (size_t) needed - avail;
  *bufcntp += needed;
  return 0;
}


static int
FCT_imm$w (struct output_data *d)
{
  if ((d->data[d->opoff2 / 8] & (1 << (7 - (d->opoff2 & 7)))) != 0)
    return FCT_imm (d);

  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  if (*d->param_start>= d->end)
    return -1;
  uint_fast8_t word = *(*d->param_start)++;
  int needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIxFAST8, word);
  if ((size_t) needed > avail)
    return (size_t) needed - avail;
  *bufcntp += needed;
  return 0;
}


#ifdef X86_64
static int
FCT_imm64$w (struct output_data *d)
{
  if ((d->data[d->opoff2 / 8] & (1 << (7 - (d->opoff2 & 7)))) == 0
      || (*d->prefixes & has_data16) != 0)
    return FCT_imm$w (d);

  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  int needed;
  if (*d->prefixes & has_rex_w)
    {
      if (*d->param_start + 8 > d->end)
	return -1;
      uint64_t word = read_8ubyte_unaligned_inc (*d->param_start);
      needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx64, word);
    }
  else
    {
      if (*d->param_start + 4 > d->end)
	return -1;
      int32_t word = read_4sbyte_unaligned_inc (*d->param_start);
      needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx32, word);
    }
  if ((size_t) needed > avail)
    return (size_t) needed - avail;
  *bufcntp += needed;
  return 0;
}
#endif


static int
FCT_imms (struct output_data *d)
{
  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  if (*d->param_start>= d->end)
    return -1;
  int8_t byte = *(*d->param_start)++;
#ifdef X86_64
  int needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx64,
			 (int64_t) byte);
#else
  int needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx32,
			 (int32_t) byte);
#endif
  if ((size_t) needed > avail)
    return (size_t) needed - avail;
  *bufcntp += needed;
  return 0;
}


static int
FCT_imm$s (struct output_data *d)
{
  uint_fast8_t opcode = d->data[d->opoff2 / 8];
  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  if ((opcode & 2) != 0)
    return FCT_imms (d);

  if ((*d->prefixes & has_data16) == 0)
    {
      if (*d->param_start + 4 > d->end)
	return -1;
      int32_t word = read_4sbyte_unaligned_inc (*d->param_start);
#ifdef X86_64
      int needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx64,
			     (int64_t) word);
#else
      int needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx32, word);
#endif
      if ((size_t) needed > avail)
	return (size_t) needed - avail;
      *bufcntp += needed;
    }
  else
    {
      if (*d->param_start + 2 > d->end)
	return -1;
      uint16_t word = read_2ubyte_unaligned_inc (*d->param_start);
      int needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx16, word);
      if ((size_t) needed > avail)
	return (size_t) needed - avail;
      *bufcntp += needed;
    }
  return 0;
}


static int
FCT_imm16 (struct output_data *d)
{
  if (*d->param_start + 2 > d->end)
    return -1;
  uint16_t word = read_2ubyte_unaligned_inc (*d->param_start);
  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  int needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx16, word);
  if ((size_t) needed > avail)
    return (size_t) needed - avail;
  *bufcntp += needed;
  return 0;
}


static int
FCT_imms8 (struct output_data *d)
{
  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  if (*d->param_start >= d->end)
    return -1;
  int_fast8_t byte = *(*d->param_start)++;
  int needed;
#ifdef X86_64
  if (*d->prefixes & has_rex_w)
    needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx64,
		       (int64_t) byte);
  else
#endif
    needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx32,
		       (int32_t) byte);
  if ((size_t) needed > avail)
    return (size_t) needed - avail;
  *bufcntp += needed;
  return 0;
}


static int
FCT_imm8 (struct output_data *d)
{
  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  if (*d->param_start >= d->end)
    return -1;
  uint_fast8_t byte = *(*d->param_start)++;
  int needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx32,
			 (uint32_t) byte);
  if ((size_t) needed > avail)
    return (size_t) needed - avail;
  *bufcntp += needed;
  return 0;
}


static int
FCT_rel (struct output_data *d)
{
  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  if (*d->param_start + 4 > d->end)
    return -1;
  int32_t rel = read_4sbyte_unaligned_inc (*d->param_start);
#ifdef X86_64
  int needed = snprintf (&d->bufp[*bufcntp], avail, "0x%" PRIx64,
			 (uint64_t) (d->addr + rel
				     + (*d->param_start - d->data)));
#else
  int needed = snprintf (&d->bufp[*bufcntp], avail, "0x%" PRIx32,
			 (uint32_t) (d->addr + rel
				     + (*d->param_start - d->data)));
#endif
  if ((size_t) needed > avail)
    return (size_t) needed - avail;
  *bufcntp += needed;
  return 0;
}


static int
FCT_mmxreg (struct output_data *d)
{
  uint_fast8_t byte = d->data[d->opoff1 / 8];
  assert (d->opoff1 % 8 == 2 || d->opoff1 % 8 == 5);
  byte = (byte >> (5 - d->opoff1 % 8)) & 7;
  size_t *bufcntp =  d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  int needed = snprintf (&d->bufp[*bufcntp], avail, "%%mm%" PRIxFAST8, byte);
  if ((size_t) needed > avail)
    return needed - avail;
  *bufcntp += needed;
  return 0;
}


static int
FCT_mod$r_m (struct output_data *d)
{
  assert (d->opoff1 % 8 == 0);
  uint_fast8_t modrm = d->data[d->opoff1 / 8];
  if ((modrm & 0xc0) == 0xc0)
    {
      int prefixes = *d->prefixes;
      if (prefixes & has_addr16)
	return -1;

      int is_16bit = (prefixes & has_data16) != 0;

      size_t *bufcntp = d->bufcntp;
      char *bufp = d->bufp;
      if (*bufcntp + 5 - is_16bit > d->bufsize)
	return *bufcntp + 5 - is_16bit - d->bufsize;
      bufp[(*bufcntp)++] = '%';

      char *cp;
#ifdef X86_64
      if ((prefixes & has_rex_b) != 0 && !is_16bit)
	{
	  cp = stpcpy (&bufp[*bufcntp], hiregs[modrm & 7]);
	  if ((prefixes & has_rex_w) == 0)
	    *cp++ = 'd';
	}
      else
#endif
	{
	  cp = stpcpy (&bufp[*bufcntp], dregs[modrm & 7] + is_16bit);
#ifdef X86_64
	  if ((prefixes & has_rex_w) != 0)
	    bufp[*bufcntp] = 'r';
#endif
	}
      *bufcntp = cp - bufp;
      return 0;
    }

  return general_mod$r_m (d);
}


#ifndef X86_64
static int
FCT_moda$r_m (struct output_data *d)
{
  assert (d->opoff1 % 8 == 0);
  uint_fast8_t modrm = d->data[d->opoff1 / 8];
  if ((modrm & 0xc0) == 0xc0)
    {
      if (*d->prefixes & has_addr16)
	return -1;

      size_t *bufcntp = d->bufcntp;
      if (*bufcntp + 3 > d->bufsize)
	return *bufcntp + 3 - d->bufsize;

      memcpy (&d->bufp[*bufcntp], "???", 3);
      *bufcntp += 3;

      return 0;
    }

  return general_mod$r_m (d);
}
#endif


#ifdef X86_64
static const char rex_8bit[8][3] =
  {
    [0] = "a", [1] = "c", [2] = "d", [3] = "b",
    [4] = "sp", [5] = "bp", [6] = "si", [7] = "di"
  };
#endif


static int
FCT_mod$r_m$w (struct output_data *d)
{
  assert (d->opoff1 % 8 == 0);
  const uint8_t *data = d->data;
  uint_fast8_t modrm = data[d->opoff1 / 8];
  if ((modrm & 0xc0) == 0xc0)
    {
      int prefixes = *d->prefixes;

      if (prefixes & has_addr16)
	return -1;

      size_t *bufcntp = d->bufcntp;
      char *bufp = d->bufp;
      if (*bufcntp + 5 > d->bufsize)
	return *bufcntp + 5 - d->bufsize;

      if ((data[d->opoff3 / 8] & (1 << (7 - (d->opoff3 & 7)))) == 0)
	{
	  bufp[(*bufcntp)++] = '%';

#ifdef X86_64
	  if (prefixes & has_rex)
	    {
	      if (prefixes & has_rex_r)
		*bufcntp += snprintf (bufp + *bufcntp, d->bufsize - *bufcntp,
				      "r%db", 8 + (modrm & 7));
	      else
		{
		  char *cp = stpcpy (bufp + *bufcntp, hiregs[modrm & 7]);
		  *cp++ = 'l';
		  *bufcntp = cp - bufp;
		}
	    }
	  else
#endif
	    {
	      bufp[(*bufcntp)++] = "acdb"[modrm & 3];
	      bufp[(*bufcntp)++] = "lh"[(modrm & 4) >> 2];
	    }
	}
      else
	{
	  int is_16bit = (prefixes & has_data16) != 0;

	  bufp[(*bufcntp)++] = '%';

	  char *cp;
#ifdef X86_64
	  if ((prefixes & has_rex_b) != 0 && !is_16bit)
	    {
	      cp = stpcpy (&bufp[*bufcntp], hiregs[modrm & 7]);
	      if ((prefixes & has_rex_w) == 0)
		*cp++ = 'd';
	    }
	  else
#endif
	    {
	      cp = stpcpy (&bufp[*bufcntp], dregs[modrm & 7] + is_16bit);
#ifdef X86_64
	      if ((prefixes & has_rex_w) != 0)
		bufp[*bufcntp] = 'r';
#endif
	    }
	  *bufcntp = cp - bufp;
	}
      return 0;
    }

  return general_mod$r_m (d);
}


static int
FCT_mod$8r_m (struct output_data *d)
{
  assert (d->opoff1 % 8 == 0);
  uint_fast8_t modrm = d->data[d->opoff1 / 8];
  if ((modrm & 0xc0) == 0xc0)
    {
      size_t *bufcntp = d->bufcntp;
      char *bufp = d->bufp;
      if (*bufcntp + 3 > d->bufsize)
	return *bufcntp + 3 - d->bufsize;
      bufp[(*bufcntp)++] = '%';
      bufp[(*bufcntp)++] = "acdb"[modrm & 3];
      bufp[(*bufcntp)++] = "lh"[(modrm & 4) >> 2];
      return 0;
    }

  return general_mod$r_m (d);
}


static int
FCT_mod$16r_m (struct output_data *d)
{
  assert (d->opoff1 % 8 == 0);
  uint_fast8_t modrm = d->data[d->opoff1 / 8];
  if ((modrm & 0xc0) == 0xc0)
    {
      assert (d->opoff1 / 8 == d->opoff2 / 8);
      //uint_fast8_t byte = data[opoff2 / 8] & 7;
      uint_fast8_t byte = modrm & 7;

      size_t *bufcntp = d->bufcntp;
      if (*bufcntp + 3 > d->bufsize)
	return *bufcntp + 3 - d->bufsize;
      d->bufp[(*bufcntp)++] = '%';
      memcpy (&d->bufp[*bufcntp], dregs[byte] + 1, sizeof (dregs[0]) - 1);
      *bufcntp += 2;
      return 0;
    }

  return general_mod$r_m (d);
}


#ifdef X86_64
static int
FCT_mod$64r_m (struct output_data *d)
{
  assert (d->opoff1 % 8 == 0);
  uint_fast8_t modrm = d->data[d->opoff1 / 8];
  if ((modrm & 0xc0) == 0xc0)
    {
      assert (d->opoff1 / 8 == d->opoff2 / 8);
      //uint_fast8_t byte = data[opoff2 / 8] & 7;
      uint_fast8_t byte = modrm & 7;

      size_t *bufcntp = d->bufcntp;
      if (*bufcntp + 4 > d->bufsize)
	return *bufcntp + 4 - d->bufsize;
      char *cp = &d->bufp[*bufcntp];
      *cp++ = '%';
      cp = stpcpy (cp,
		   (*d->prefixes & has_rex_b) ? hiregs[byte] : aregs[byte]);
      *bufcntp = cp - d->bufp;
      return 0;
    }

  return general_mod$r_m (d);
}
#else
static typeof (FCT_mod$r_m) FCT_mod$64r_m __attribute__ ((alias ("FCT_mod$r_m")));
#endif


static int
FCT_reg (struct output_data *d)
{
  uint_fast8_t byte = d->data[d->opoff1 / 8];
  assert (d->opoff1 % 8 + 3 <= 8);
  byte >>= 8 - (d->opoff1 % 8 + 3);
  byte &= 7;
  int is_16bit = (*d->prefixes & has_data16) != 0;
  size_t *bufcntp = d->bufcntp;
  if (*bufcntp + 5 > d->bufsize)
    return *bufcntp + 5 - d->bufsize;
  d->bufp[(*bufcntp)++] = '%';
#ifdef X86_64
  if ((*d->prefixes & has_rex_r) != 0 && !is_16bit)
    {
      *bufcntp += snprintf (&d->bufp[*bufcntp], d->bufsize - *bufcntp, "r%d",
			    8 + byte);
      if ((*d->prefixes & has_rex_w) == 0)
	d->bufp[(*bufcntp)++] = 'd';
    }
  else
#endif
    {
      memcpy (&d->bufp[*bufcntp], dregs[byte] + is_16bit, 3 - is_16bit);
#ifdef X86_64
      if ((*d->prefixes & has_rex_w) != 0 && !is_16bit)
	d->bufp[*bufcntp] = 'r';
#endif
      *bufcntp += 3 - is_16bit;
    }
  return 0;
}


#ifdef X86_64
static int
FCT_oreg (struct output_data *d)
{
  /* Special form where register comes from opcode.  The rex.B bit is used,
     rex.R and rex.X are ignored.  */
  int save_prefixes = *d->prefixes;

  *d->prefixes = ((save_prefixes & ~has_rex_r)
		  | ((save_prefixes & has_rex_b) << (idx_rex_r - idx_rex_b)));

  int r = FCT_reg (d);

  *d->prefixes = save_prefixes;

  return r;
}
#endif


static int
FCT_reg64 (struct output_data *d)
{
  uint_fast8_t byte = d->data[d->opoff1 / 8];
  assert (d->opoff1 % 8 + 3 <= 8);
  byte >>= 8 - (d->opoff1 % 8 + 3);
  byte &= 7;
  if ((*d->prefixes & has_data16) != 0)
    return -1;
  size_t *bufcntp = d->bufcntp;
  if (*bufcntp + 5 > d->bufsize)
    return *bufcntp + 5 - d->bufsize;
  d->bufp[(*bufcntp)++] = '%';
#ifdef X86_64
  if ((*d->prefixes & has_rex_r) != 0)
    {
      *bufcntp += snprintf (&d->bufp[*bufcntp], d->bufsize - *bufcntp, "r%d",
			    8 + byte);
      if ((*d->prefixes & has_rex_w) == 0)
	d->bufp[(*bufcntp)++] = 'd';
    }
  else
#endif
    {
      memcpy (&d->bufp[*bufcntp], aregs[byte], 3);
      *bufcntp += 3;
    }
  return 0;
}


static int
FCT_reg$w (struct output_data *d)
{
  if (d->data[d->opoff2 / 8] & (1 << (7 - (d->opoff2 & 7))))
    return FCT_reg (d);

  uint_fast8_t byte = d->data[d->opoff1 / 8];
  assert (d->opoff1 % 8 + 3 <= 8);
  byte >>= 8 - (d->opoff1 % 8 + 3);
  byte &= 7;

  size_t *bufcntp = d->bufcntp;
  if (*bufcntp + 4 > d->bufsize)
    return *bufcntp + 4 - d->bufsize;

  d->bufp[(*bufcntp)++] = '%';

#ifdef X86_64
  if (*d->prefixes & has_rex)
    {
      if (*d->prefixes & has_rex_r)
	*bufcntp += snprintf (d->bufp + *bufcntp, d->bufsize - *bufcntp,
			      "r%db", 8 + byte);
      else
	{
	  char* cp = stpcpy (d->bufp + *bufcntp, rex_8bit[byte]);
	  *cp++ = 'l';
	  *bufcntp = cp - d->bufp;
	}
    }
  else
#endif
    {
      d->bufp[(*bufcntp)++] = "acdb"[byte & 3];
      d->bufp[(*bufcntp)++] = "lh"[byte >> 2];
    }
  return 0;
}


#ifdef X86_64
static int
FCT_oreg$w (struct output_data *d)
{
  /* Special form where register comes from opcode.  The rex.B bit is used,
     rex.R and rex.X are ignored.  */
  int save_prefixes = *d->prefixes;

  *d->prefixes = ((save_prefixes & ~has_rex_r)
		  | ((save_prefixes & has_rex_b) << (idx_rex_r - idx_rex_b)));

  int r = FCT_reg$w (d);

  *d->prefixes = save_prefixes;

  return r;
}
#endif


static int
FCT_freg (struct output_data *d)
{
  assert (d->opoff1 / 8 == 1);
  assert (d->opoff1 % 8 == 5);
  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  int needed = snprintf (&d->bufp[*bufcntp], avail, "%%st(%" PRIx32 ")",
			 (uint32_t) (d->data[1] & 7));
  if ((size_t) needed > avail)
    return (size_t) needed - avail;
  *bufcntp += needed;
  return 0;
}


#ifndef X86_64
static int
FCT_reg16 (struct output_data *d)
{
  if (*d->prefixes & has_data16)
    return -1;

  *d->prefixes |= has_data16;
  return FCT_reg (d);
}
#endif


static int
FCT_sel (struct output_data *d)
{
  assert (d->opoff1 % 8 == 0);
  assert (d->opoff1 / 8 == 5);
  if (*d->param_start + 2 > d->end)
    return -1;
  *d->param_start += 2;
  uint16_t absval = read_2ubyte_unaligned (&d->data[5]);

  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  int needed = snprintf (&d->bufp[*bufcntp], avail, "$0x%" PRIx16, absval);
  if ((size_t) needed > avail)
    return needed - avail;
  *bufcntp += needed;
  return 0;
}


static int
FCT_sreg2 (struct output_data *d)
{
  uint_fast8_t byte = d->data[d->opoff1 / 8];
  assert (d->opoff1 % 8 + 3 <= 8);
  byte >>= 8 - (d->opoff1 % 8 + 2);

  size_t *bufcntp = d->bufcntp;
  char *bufp = d->bufp;
  if (*bufcntp + 3 > d->bufsize)
    return *bufcntp + 3 - d->bufsize;

  bufp[(*bufcntp)++] = '%';
  bufp[(*bufcntp)++] = "ecsd"[byte & 3];
  bufp[(*bufcntp)++] = 's';

  return 0;
}


static int
FCT_sreg3 (struct output_data *d)
{
  uint_fast8_t byte = d->data[d->opoff1 / 8];
  assert (d->opoff1 % 8 + 4 <= 8);
  byte >>= 8 - (d->opoff1 % 8 + 3);

  if ((byte & 7) >= 6)
    return -1;

  size_t *bufcntp = d->bufcntp;
  char *bufp = d->bufp;
  if (*bufcntp + 3 > d->bufsize)
    return *bufcntp + 3 - d->bufsize;

  bufp[(*bufcntp)++] = '%';
  bufp[(*bufcntp)++] = "ecsdfg"[byte & 7];
  bufp[(*bufcntp)++] = 's';

  return 0;
}


static int
FCT_string (struct output_data *d __attribute__ ((unused)))
{
  return 0;
}


static int
FCT_xmmreg (struct output_data *d)
{
  uint_fast8_t byte = d->data[d->opoff1 / 8];
  assert (d->opoff1 % 8 == 2 || d->opoff1 % 8 == 5);
  byte = (byte >> (5 - d->opoff1 % 8)) & 7;

  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  int needed = snprintf (&d->bufp[*bufcntp], avail, "%%xmm%" PRIxFAST8, byte);
  if ((size_t) needed > avail)
    return needed - avail;
  *bufcntp += needed;
  return 0;
}
