/* Operand description structure */
struct powerpc_operand
{
  int bits;
  int shift;
  uint32_t (*insert)(uint32_t,int32_t,const char **);
  uint32_t flags;
};

/* powerpc_operand flags */
#define OPER_SIGNED   (1)        /* signed values */
#define OPER_SIGNOPT  (2)        /* signed values up to 0xffff */
#define OPER_FAKE     (4)        /* just reuse last read operand */
#define OPER_PARENS   (8)        /* operand is in parentheses */
#define OPER_CR       (0x10)     /* CR field */
#define OPER_GPR      (0x20)     /* GPR field */
#define OPER_FPR      (0x40)     /* FPR field */
#define OPER_RELATIVE (0x80)     /* relative branch displacement */
#define OPER_ABSOLUTE (0x100)    /* absolute branch address */
#define OPER_OPTIONAL (0x200)    /* optional, zero if omitted */
#define OPER_NEXT     (0x400)    /* hack for rotate instructions */
#define OPER_NEGATIVE (0x800)    /* range check on negative value */
#define OPER_VR       (0x1000)   /* Altivec register field */

/* Operand types. */
enum {
  UNUSED,BA,BAT,BB,BBA,BD,BDA,BDM,BDMA,BDP,BDPA,BF,OBF,BFA,BI,BO,BOE,
  BT,CR,D,DS,E,FL1,FL2,FLM,FRA,FRB,FRC,FRS,FXM,L,LEV,LI,LIA,MB,ME,
  MBE,MBE_,MB6,NB,NSI,RA,RAL,RAM,RAS,RB,RBS,RS,SH,SH6,SI,SISIGNOPT,
  SPR,SPRBAT,SPRG,SR,SV,TBR,TO,U,UI,VA,VB,VC,VD,SIMM,UIMM,SHB,
  SLWI,SRWI,EXTLWI,EXTRWI,EXTWIB,INSLWI,INSRWI,ROTRWI,CLRRWI,CLRLSL,
  STRM,AT
};

#define FRT FRS
#define ME6 MB6
#define RT RS
#define VS VD
#define NEXT (-1)  /* use operand_type+1 for next operand */


/* The functions used to insert complex operands. */

static uint32_t insert_bat(uint32_t insn,int32_t value,const char **errmsg)
{
  return insn | (((insn >> 21) & 0x1f) << 16);
}

static uint32_t insert_bba(uint32_t insn,int32_t value,const char **errmsg)
{
  return insn | (((insn >> 16) & 0x1f) << 11);
}

static uint32_t insert_bd(uint32_t insn,int32_t value,const char **errmsg)
{
  return insn | (value & 0xfffc);
}

static uint32_t insert_bdm(uint32_t insn,int32_t value,const char **errmsg)
{
  if ((value & 0x8000) != 0)
    insn |= 1 << 21;
  return insn | (value & 0xfffc);
}

static uint32_t insert_bdp(uint32_t insn,int32_t value,const char **errmsg)
{
  if ((value & 0x8000) == 0)
    insn |= 1 << 21;
  return insn | (value & 0xfffc);
}

static int valid_bo(int32_t value)
{
  switch (value & 0x14) {
    default:
    case 0:
      return 1;
    case 0x4:
      return (value & 0x2) == 0;
    case 0x10:
      return (value & 0x8) == 0;
    case 0x14:
      return value == 0x14;
  }
}

static uint32_t insert_bo(uint32_t insn,int32_t value,const char **errmsg)
{
  if (!valid_bo (value))
    *errmsg = "invalid conditional option";
  return insn | ((value & 0x1f) << 21);
}

static uint32_t insert_boe(uint32_t insn,int32_t value,const char **errmsg)
{
  if (!valid_bo (value))
    *errmsg = "invalid conditional option";
  else if ((value & 1) != 0)
    *errmsg = "attempt to set y bit when using + or - modifier";
  return insn | ((value & 0x1f) << 21);
}

static uint32_t insert_ds(uint32_t insn,int32_t value,const char **errmsg)
{
  return insn | (value & 0xfffc);
}

static uint32_t insert_li(uint32_t insn,int32_t value,const char **errmsg)
{
  if ((value & 3) != 0)
    *errmsg = "ignoring least significant bits in branch offset";
  return insn | (value & 0x3fffffc);
}

static uint32_t insert_mbe(uint32_t insn,int32_t value,const char **errmsg)
{
  uint32_t uval, mask;
  int mb, me, mx, count, last;

  uval = value;

  if (uval == 0) {
      *errmsg = "illegal bitmask";
      return insn;
  }

  mb = 0;
  me = 32;
  if ((uval & 1) != 0)
    last = 1;
  else
    last = 0;
  count = 0;

  for (mx = 0, mask = (int32_t) 1 << 31; mx < 32; ++mx, mask >>= 1) {
    if ((uval & mask) && !last) {
      ++count;
      mb = mx;
      last = 1;
    }
    else if (!(uval & mask) && last) {
      ++count;
      me = mx;
      last = 0;
    }
  }
  if (me == 0)
    me = 32;

  if (count != 2 && (count != 0 || ! last)) {
    *errmsg = "illegal bitmask";
  }

  return insn | (mb << 6) | ((me - 1) << 1);
}

static uint32_t insert_mb6(uint32_t insn,int32_t value,const char **errmsg)
{
  return insn | ((value & 0x1f) << 6) | (value & 0x20);
}

static uint32_t insert_nb(uint32_t insn,int32_t value,const char **errmsg)
{
  if (value < 0 || value > 32)
    *errmsg = "value out of range";
  if (value == 32)
    value = 0;
  return insn | ((value & 0x1f) << 11);
}

static uint32_t insert_nsi(uint32_t insn,int32_t value,const char **errmsg)
{
  return insn | ((- value) & 0xffff);
}

static uint32_t insert_ral(uint32_t insn,int32_t value,const char **errmsg)
{
  if (value == 0
      || (uint32_t) value == ((insn >> 21) & 0x1f))
    *errmsg = "invalid register operand when updating";
  return insn | ((value & 0x1f) << 16);
}

static uint32_t insert_ram(uint32_t insn,int32_t value,const char **errmsg)
{
  if ((uint32_t) value >= ((insn >> 21) & 0x1f))
    *errmsg = "index register in load range";
  return insn | ((value & 0x1f) << 16);
}

static uint32_t insert_ras(uint32_t insn,int32_t value,const char **errmsg)
{
  if (value == 0)
    *errmsg = "invalid register operand when updating";
  return insn | ((value & 0x1f) << 16);
}

static uint32_t insert_rbs(uint32_t insn,int32_t value,const char **errmsg)
{
  return insn | (((insn >> 21) & 0x1f) << 11);
}

static uint32_t insert_sh6(uint32_t insn,int32_t value,const char **errmsg)
{
  return insn | ((value & 0x1f) << 11) | ((value & 0x20) >> 4);
}

static uint32_t insert_spr(uint32_t insn,int32_t value,const char **errmsg)
{
  return insn | ((value & 0x1f) << 16) | ((value & 0x3e0) << 6);
}

static uint32_t insert_tbr(uint32_t insn,int32_t value,const char **errmsg)
{
  if (value == 0)
    value = 268;
  return insn | ((value & 0x1f) << 16) | ((value & 0x3e0) << 6);
}

static uint32_t insert_slwi(uint32_t insn,int32_t value,const char **errmsg)
{
  return insn | ((value&0x1f)<<11) | ((31-(value&0x1f))<<1);
}

static uint32_t insert_srwi(uint32_t insn,int32_t value,const char **errmsg)
{
  return insn | (((32-value)&0x1f)<<11) | ((value&0x1f)<<6) | (31<<1);
}

static uint32_t insert_extlwi(uint32_t insn,int32_t value,const char **errmsg)
{
  if (value<1 || value>32)
    *errmsg = "value out of range (1-32)";
  return insn | (((value-1)&0x1f)<<1);
}

static uint32_t insert_extrwi(uint32_t insn,int32_t value,const char **errmsg)
{
  if (value<1 || value>32)
    *errmsg = "value out of range (1-32)";
  return insn | ((value&0x1f)<<11) | (((32-value)&0x1f)<<6) | (31<<1);
}

static uint32_t insert_extwib(uint32_t insn,int32_t value,const char **errmsg)
{
  value += (insn>>11) & 0x1f;
  if (value > 32)
    *errmsg = "sum of last two operands out of range (0-32)";
  return (insn&~0xf800) | ((value&0x1f)<<11);
}

static uint32_t insert_inslwi(uint32_t insn,int32_t value,const char **errmsg)
{
  int32_t n = ((insn>>1) & 0x1f) + 1;
  if (value+n > 32)
    *errmsg = "sum of last two operands out of range (1-32)";
  return (insn&~0xfffe) | (((32-value)&0x1f)<<11) | ((value&0x1f)<<6)
                        | ((((value+n)-1)&0x1f)<<1);
}

static uint32_t insert_insrwi(uint32_t insn,int32_t value,const char **errmsg)
{
  int32_t n = ((insn>>1) & 0x1f) + 1;
  if (value+n > 32)
    *errmsg = "sum of last two operands out of range (1-32)";
  return (insn&~0xfffe) | (((32-(value+n))&0x1f)<<11) | ((value&0x1f)<<6)
                        | ((((value+n)-1)&0x1f)<<1);
}

static uint32_t insert_rotrwi(uint32_t insn,int32_t value,const char **errmsg)
{
  return insn | (((32-value)&0x1f)<<11);
}

static uint32_t insert_clrrwi(uint32_t insn,int32_t value,const char **errmsg)
{
  return insn | (((31-value)&0x1f)<<1);
}

static uint32_t insert_clrlslwi(uint32_t insn,int32_t value,const char **errmsg)
{
  int32_t b = (insn>>6) & 0x1f;
  if (value > b)
    *errmsg = "n (4th oper) must be less or equal to b (3rd oper)";
  return (insn&~0x7c0) | ((value&0x1f)<<11) | (((b-value)&0x1f)<<6)
                       | (((31-value)&0x1f)<<1);
}


/* The operands table.
   The fields are: bits, shift, insert, flags. */

const struct powerpc_operand powerpc_operands[] =
{
  /* UNUSED */
  { 0, 0, 0, 0 },

  /* BA */
  { 5, 16, 0, OPER_CR },

  /* BAT */
  { 5, 16, insert_bat, OPER_FAKE },

  /* BB */
  { 5, 11, 0, OPER_CR },

  /* BBA */
  { 5, 11, insert_bba, OPER_FAKE },

  /* BD */
  { 16, 0, insert_bd, OPER_RELATIVE | OPER_SIGNED },

  /* BDA */
  { 16, 0, insert_bd, OPER_ABSOLUTE | OPER_SIGNED },

  /* BDM */
  { 16, 0, insert_bdm, OPER_RELATIVE | OPER_SIGNED },

  /* BDMA */
  { 16, 0, insert_bdm, OPER_ABSOLUTE | OPER_SIGNED },

  /* BDP */
  { 16, 0, insert_bdp, OPER_RELATIVE | OPER_SIGNED },

  /* BDPA */
  { 16, 0, insert_bdp, OPER_ABSOLUTE | OPER_SIGNED },

  /* BF */
  { 3, 23, 0, OPER_CR },

  /* OBF */
  { 3, 23, 0, OPER_CR | OPER_OPTIONAL },

  /* BFA */
  { 3, 18, 0, OPER_CR },

  /* BI */
  { 5, 16, 0, OPER_CR },

  /* BO */
  { 5, 21, insert_bo, 0 },

  /* BOE */
  { 5, 21, insert_boe, 0 },

  /* BT */
  { 5, 21, 0, OPER_CR },

  /* CR */
  { 3, 18, 0, OPER_CR | OPER_OPTIONAL },

  /* D */
  { 16, 0, 0, OPER_PARENS | OPER_SIGNED },

  /* DS */
  { 16, 0, insert_ds, OPER_PARENS | OPER_SIGNED },

  /* E */
  { 1, 15, 0, 0 },

  /* FL1 */
  { 4, 12, 0, 0 },

  /* FL2 */
  { 3, 2, 0, 0 },

  /* FLM */
  { 8, 17, 0, 0 },

  /* FRA */
  { 5, 16, 0, OPER_FPR },

  /* FRB */
  { 5, 11, 0, OPER_FPR },

  /* FRC */
  { 5, 6, 0, OPER_FPR },

  /* FRS */
  { 5, 21, 0, OPER_FPR },

  /* FXM */
  { 8, 12, 0, 0 },

  /* L */
  { 1, 21, 0, OPER_OPTIONAL },

  /* LEV */
  { 7, 5, 0, 0 },

  /* LI */
  { 26, 0, insert_li, OPER_RELATIVE | OPER_SIGNED },

  /* LIA */
  { 26, 0, insert_li, OPER_ABSOLUTE | OPER_SIGNED },

  /* MB */
  { 5, 6, 0, 0 },

  /* ME */
  { 5, 1, 0, 0 },

  /* MBE */
  { 5, 6, 0, OPER_OPTIONAL | OPER_NEXT },
  /* MBE_ (NEXT) */
  { 31, 1, insert_mbe, 0 },

  /* MB6 */
  { 6, 5, insert_mb6, 0 },

  /* NB */
  { 6, 11, insert_nb, 0 },

  /* NSI */
  { 16, 0, insert_nsi, OPER_NEGATIVE | OPER_SIGNED },

  /* RA */
  { 5, 16, 0, OPER_GPR },

  /* RAL */
  { 5, 16, insert_ral, OPER_GPR },

  /* RAM */
  { 5, 16, insert_ram, OPER_GPR },

  /* RAS */
  { 5, 16, insert_ras, OPER_GPR },

  /* RB */
  { 5, 11, 0, OPER_GPR },

  /* RBS */
  { 5, 1, insert_rbs, OPER_FAKE },

  /* RS */
  { 5, 21, 0, OPER_GPR },

  /* SH */
  { 5, 11, 0, 0 },

  /* SH6 */
  { 6, 1, insert_sh6, 0 },

  /* SI */
  { 16, 0, 0, OPER_SIGNED },

  /* SISIGNOPT */
  { 16, 0, 0, OPER_SIGNED | OPER_SIGNOPT },

  /* SPR */
  { 10, 11, insert_spr, 0 },

  /* SPRBAT */
  { 2, 17, 0, 0 },

  /* SPRG */
  { 2, 16, 0, 0 },

  /* SR */
  { 4, 16, 0, 0 },

  /* SV */
  { 14, 2, 0, 0 },

  /* TBR */
  { 10, 11, insert_tbr, OPER_OPTIONAL },

  /* TO */
  { 5, 21, 0, 0 },

  /* U */
  { 4, 12, 0, 0 },

  /* UI */
  { 16, 0, 0, 0 },

  /* VA */
  { 5, 16, 0, OPER_VR },

  /* VB */
  { 5, 11, 0, OPER_VR }, 

  /* VC */
  { 5, 6, 0, OPER_VR },

  /* VD */
  { 5, 21, 0, OPER_VR },

  /* SIMM */
  { 5, 16, 0, OPER_SIGNED},

  /* UIMM */
  { 5, 16, 0, 0 },

  /* SHB */
  { 4, 6, 0, 0 },

  /* SLWI */
  { 5, 11, insert_slwi, 0 },

  /* SRWI */
  { 5, 11, insert_srwi, 0 },

  /* EXTLWI */
  { 31, 1, insert_extlwi, 0 },

  /* EXTRWI */
  { 31, 1, insert_extrwi, 0 },

  /* EXTWIB */
  { 5, 11, insert_extwib, 0 },

  /* INSLWI */
  { 5, 11, insert_inslwi, 0 },

  /* INSRWI */
  { 5, 11, insert_insrwi, 0 },

  /* ROTRWI */
  { 5, 11, insert_rotrwi, 0 },

  /* CLRRWI */
  { 5, 1, insert_clrrwi, 0 },

  /* CLRLSL */
  { 5, 11, insert_clrlslwi, 0 },

  /* STRM */
  { 2, 21, 0, 0 },

  /* AT */
  { 1, 25, 0, OPER_OPTIONAL },
};
