/*--------------------------------------------------------------------*/
/*--- Implementation of the floating point instruction set.        ---*/
/*---                                                     hd_fpu.c ---*/
/*--------------------------------------------------------------------*/

/*
   This file is part of Heimdall, an x86 protected-mode emulator 
   designed for debugging and profiling binaries on x86-Unixes.

   Copyright (C) 2000 Julian Seward 
      jseward@acm.org
      Julian_Seward@muraroa.demon.co.uk

   This program 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 2 of the
   License, or (at your option) any later version.

   This program 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., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA.

   The GNU General Public License is contained in the file LICENSE.
*/

#include "hd_include.h"


/* ---------------------------------------------------------------------
   Packing and unpacking the FPU data registers.
   ------------------------------------------------------------------ */

INLINE
UInt fp_get_tos ( void )
{
   return (m_fpu_state.env[FP_ENV_STAT] >> FP_F_TOS_LO) & 7;
}

static
UInt read_bit_array ( UChar* arr, UInt n )
{
   UChar c = arr[n >> 3];
   c >>= (n&7);
   return c & 1;
}

static
void write_bit_array ( UChar* arr, UInt n, UInt b )
{
   UChar c = arr[n >> 3];
   c &= ~(1 << (n&7));
   b &= 1;
   c |=  (b << (n&7));
   arr[n >> 3] = c;
}

/* Read an IEEE double from the memory image of an Intel 80-bit
   extended floating-point number.
*/
static
double fp_double_from_extended ( UChar* e_lsb )
{
   int i;
   double d;
   UChar* d_lsb = (UChar*)(&d);

   UInt sign = e_lsb[9] >> 7;
   Int bexp = ((UInt)e_lsb[9] << 8) | (UInt)e_lsb[8];
   bexp &= 0x7fff;

   if (bexp == 0) 
      bexp = 0;  /* preserve zeroes */
   else 
   if (bexp == 0x7FFF) 
      bexp = 0x7FF; /* preserve Infs/Nans */
   else {
      bexp -= (16383 - 1023);
      if (bexp < 0) bexp = 0;
      if (bexp > 0x7FF) bexp = 0x7FF;
   }

   d_lsb[6] = (bexp & 0xF) << 4;
   d_lsb[7] = ((bexp >> 4) & 0x7F) | ((sign & 0x1) << 7);

   for (i = 0; i < 52; i++)
      write_bit_array ( d_lsb,
                        i,
                        read_bit_array ( e_lsb, i+11 ) );
   return d;
}

/* Given an IEEE double, create the memory image of an Intel 80-bit
   extended floating-point number.
*/
static
void fp_extended_from_double ( UChar* e_lsb, double d )
{
   int i;
   UChar* d_lsb = (UChar*)(&d);

   UInt sign = d_lsb[7] >> 7;
   Int bexp = ((UInt)d_lsb[7] << 4) |
               ((((UInt)d_lsb[6]) >> 4) & 0xF);
   bexp &= 0x7ff;

   if (bexp == 0) 
      bexp = 0;  /* preserve zeroes */
   else 
   if (bexp == 0x7FF) 
      bexp = 0x7FFF; /* preserve Infs/Nans */
   else
      bexp += (16383 - 1023);

   e_lsb[9] = ((bexp >> 8) & 0x7F) | ((sign & 0x1) << 7);
   e_lsb[8] = bexp & 0xFF;

   for (i = 0; i < 52; i++)
      write_bit_array ( e_lsb,
                        i+11,
                        read_bit_array ( d_lsb, i ) );
   for (i = 0; i < 11; i++)
      write_bit_array ( e_lsb, i, 0 );

   /* this isn't really right, but I can't get fpclassify to work. */
   i = 0;
   if (isnan(d) || isinf(d) || d != 0.0) i = 1;
   write_bit_array ( e_lsb, 63, i );
}

/* For the transition Real CPU -> Simulated CPU, copy the 
   .reg values in m_fpu_state, which are in stack order, to
   the m_fpu_data_regs array, in register (non-stack) order.
*/
void fp_unpack_data_regs ( void )
{
   Int reg, st;
   reg = fp_get_tos();
   for (st = 0; st < 8; st++) {
      m_fpu_data_regs[reg]
         = fp_double_from_extended ( &m_fpu_state.reg[FP_REG(st)] );
      if (reg == 7) reg = 0; else reg++;
   }
}

void fp_repack_data_regs ( void )
{
   Int reg, st;
   st = fp_get_tos();
   for (reg = 0; reg < 8; reg++) {
      fp_extended_from_double ( &m_fpu_state.reg[FP_REG(reg)], 
                                m_fpu_data_regs[st] );
      if (st == 7) st = 0; else st++;
   }
}

/* ---------------------------------------------------------------------
   Helper functions for the floating point unit.
   ------------------------------------------------------------------ */

static
INLINE
void setFMem ( UInt addr, double f )
{
   * ((float*)addr) = (float)f;
}

static
INLINE
double getFMem ( UInt addr )
{
   return (double) (* ((float*)addr));
}

static
INLINE
void setDMem ( UInt addr, double f )
{
   * ((double*)addr) = f;
}

static
INLINE
double getDMem ( UInt addr )
{
   return (* ((double*)addr));
}

static
INLINE
void setTMem ( UInt addr, double f )
{
   fp_extended_from_double ( (Addr)addr, f );
}

static
INLINE
double getTMem ( UInt addr )
{
   return fp_double_from_extended ( (Addr)addr );
}

#define fp_extended_from_double ERROR__fp_extended_from_double_used
#define fp_double_from_extended ERROR__fp_double_from_extended_used

static
INLINE
UInt fp_get_statusword_flag ( UInt flagno )
{
   if (flagno < 0 || flagno > 15) panic("fp_get_statusword_flag");
   return (m_fpu_state.env[FP_ENV_STAT] >> flagno) & 0x1;
}

#if DEBUG
static
UInt fp_get_controlword_flag ( UInt flagno )
{
   if (flagno < 0 || flagno > 15) panic("fp_get_controlword_flag");
   return (m_fpu_state.env[FP_ENV_CTRL] >> flagno) & 0x1;
}
#endif

static
INLINE
void fp_set_statusword_flag_to ( UInt flagno, UInt bit )
{
   if (flagno < 0 || flagno > 15) panic("fp_set_statusword_flag_to");
   if (bit)
      m_fpu_state.env[FP_ENV_STAT] |= (1 << flagno);
   else
      m_fpu_state.env[FP_ENV_STAT] &= ~(1 << flagno);
}

static
void fp_set_stack_overflow ( void )
{
   fprintf(stderr, "--- FP STACK OVERFLOW!\n" );
   fp_set_statusword_flag_to(FP_E_INVAL,1);
   fp_set_statusword_flag_to(FP_E_STACKF,1);
   fp_set_statusword_flag_to(FP_F_C1,1);
}

static
void fp_set_stack_underflow ( void )
{
   fprintf(stderr, "--- FP STACK UNDERFLOW!\n" );
   fp_set_statusword_flag_to(FP_E_INVAL,1);
   fp_set_statusword_flag_to(FP_E_STACKF,1);
   fp_set_statusword_flag_to(FP_F_C1,0);
}

static
INLINE
void fp_set_tos ( UInt tos )
{
   if (tos < 0 || tos > 7) panic("fp_set_tos");
   fp_set_statusword_flag_to(FP_F_TOS_LO,0);
   fp_set_statusword_flag_to(FP_F_TOS_LO+1,0);
   fp_set_statusword_flag_to(FP_F_TOS_HI,0);
   m_fpu_state.env[FP_ENV_STAT] |= (tos << FP_F_TOS_LO);
}

static
INLINE
UInt fp_STno_to_regno ( UInt stregno )
{
   UInt regno = fp_get_tos();
   assert(regno >= 0 && regno < 8);
   regno += stregno;
   if (regno >= 8) regno -= 8;
   assert(regno >= 0 && regno < 8);
   return regno;
}

static
INLINE
void fp_dec_tos ( void )
{
   fp_set_tos ( fp_STno_to_regno ( 7 ));
}

static
INLINE
void fp_inc_tos ( void )
{
   fp_set_tos ( fp_STno_to_regno ( 1 ));
}

static
INLINE
Bool fp_is_empty_tag ( UInt tag )
{
   return tag == FP_TAG_EMPTY;
}

static
INLINE
UInt fp_get_tag ( UInt regno )
{
   if (regno < 0 || regno > 7) panic("fp_get_tag");
   return (m_fpu_state.env[FP_ENV_TAG] >> (2*regno)) & 3;
}

static
INLINE
UInt fp_get_tag_ST ( UInt stregno )
{
   if (stregno < 0 || stregno > 7) panic("fp_get_tag_ST");
   return fp_get_tag ( fp_STno_to_regno(stregno) );
}

static
INLINE
void fp_set_tag ( UInt regno, UInt val )
{
   if (regno < 0 || regno > 7 ||
       val < 0 || val > 3) panic("fp_get_tag");
   m_fpu_state.env[FP_ENV_TAG] &= ~(3 << (2*regno));
   m_fpu_state.env[FP_ENV_TAG] |=  (val << (2*regno));
}

static
INLINE
void fp_set_tag_ST ( UInt stregno, UInt val )
{
   if (stregno < 0 || stregno > 7) panic("fp_set_tag_ST");
   fp_set_tag ( fp_STno_to_regno(stregno), val );
}


static
INLINE
void fp_set_reg ( UInt r, double d )
{
   if (r < 0 || r > 7) panic("fp_set_reg");
   m_fpu_data_regs[r] = d;
   fp_set_tag ( r, d==0.0 ? FP_TAG_ZERO 
                          : (finite(d) ? FP_TAG_VALID : FP_TAG_SPEC) );
}

static
INLINE
void fp_set_reg_ST ( UInt str, double d )
{
   UInt r;
   if (str < 0 || str > 7) panic("fp_set_reg_ST");
   r = fp_STno_to_regno(str);
   fp_set_reg ( r, d );
}

static
INLINE
double fp_get_reg ( UInt r )
{
   double d;
   if (r < 0 || r > 7) panic("fp_get_reg");
   d = m_fpu_data_regs[r];
   return d;
}

static
INLINE
double fp_get_reg_ST ( UInt str )
{
   UInt r;
   if (str < 0 || str > 7) panic("fp_get_reg_ST");
   r = fp_STno_to_regno(str);
   return fp_get_reg(r);
}

static
INLINE
void fp_set_tos_reg ( double d )
{
   fp_set_reg ( fp_get_tos(), d );
}

static
INLINE
double fp_get_tos_reg ( void )
{
   return fp_get_reg ( fp_get_tos() );
}

static
INLINE
void fp_set_tos_reg_QNaN ( void )
{
   fp_set_reg ( fp_get_tos(), NAN /* see <nan.h> */ );
}

static
INLINE
double fp_pop ( void )
{
   double d = fp_get_tos_reg();
   fp_set_tag ( fp_get_tos(), FP_TAG_EMPTY );
   fp_inc_tos();
   return d;
}

/* Push d and update flags. */
static
INLINE
void fp_push ( double d )
{
   if (fp_is_empty_tag(fp_get_tag_ST(7))) {
      fp_dec_tos();
      fp_set_tos_reg(d);
      fp_set_statusword_flag_to(FP_F_C1, d == 0.0);
   } else {
      fp_dec_tos();
      fp_set_tos_reg_QNaN();
      fp_set_stack_overflow();
   }
}

static
void fp_set_statusword_flags_COM ( double vd_dst, double vd_src )
{
   UInt vis_dst;
   if (isnan(vd_src) || isnan(vd_dst))  vis_dst = 7;
   else if (vd_dst > vd_src)            vis_dst = 0;
   else if (vd_dst < vd_src)            vis_dst = 1; 
   else if (vd_dst == vd_src)           vis_dst = 4;
   else vis_dst = 7;
   fp_set_statusword_flag_to(FP_F_C3, (vis_dst >> 2) & 1);
   fp_set_statusword_flag_to(FP_F_C2, (vis_dst >> 1) & 1);
   fp_set_statusword_flag_to(FP_F_C0, vis_dst & 1);
}

static
void fp_set_statusword_flags_COM_STACKF ( void )
{
   UInt vis_dst = 7;
   fp_set_statusword_flag_to(FP_F_C3, (vis_dst >> 2) & 1);
   fp_set_statusword_flag_to(FP_F_C2, (vis_dst >> 1) & 1);
   fp_set_statusword_flag_to(FP_F_C0, vis_dst & 1);
}

static
double fp_calc_yl2xp1 ( double st_0, double st_1 )
{
   st_0 += 1.0;
   st_0 = log(st_0) / log(2.0);
   st_0 *= st_1;
   return st_0;
}

static
double fp_calc_yl2x ( double st_0, double st_1 )
{
   st_0 = log(st_0) / log(2.0);
   st_0 *= st_1;
   return st_0;
}

static
double fp_calc_2xm1 ( double st_0 )
{
   st_0 = st_0 * 0.69314718055994530942;
   st_0 = exp(st_0);
   st_0 = st_0 - 1.0;
   return st_0;
}

static
double fp_calc_scale ( double st_0, double st_1 )
{
   Int n = 0;
   if (st_1 > 0.0) {
      if (st_1 > 2.0*308.0) st_1 = 2.0*308.0;
      n = (Int)(floor(st_1));
      if (n < 0) n = 0;          /* impossible, but ... */
      if (n > 2*308) n = 2*308;  /* limit exponent change */
      while (n > 0) { n--; st_0 *= 2.0; };
   } 
   else 
   if (st_1 < 0.0) {
      if (st_1 < -2.0*308.0) st_1 = -2.0*308.0;
      n = ((Int)(floor(-st_1)));
      if (n < 0) n = 0;
      if (n > 2*308) n = 2*308;
      while (n > 0) { n--; st_0 *= 0.5; };
   }
   return st_0;
}

static
void fp_calc_fprem ( Int* qq, double* result, double st_0, double st_1 )
{
   double tmp = st_0 / st_1;
   if (tmp < 0)
      *qq = - (Int)floor(-tmp);
   else
      *qq = (Int)floor(tmp);
   *result = st_0 - (st_1 * (double)(*qq));
}

#if DEBUG
static 
void printFpuState ( void )
{
   Int i;
   assert(sizeof(Fpu_State)==108);
   for (i = 7; i >= 0; i--) {
      printf ( " %s fpreg%d: 0x", 
               (UInt)i == fp_get_tos() ? "**" : "  ", i );
      //for (j = FP_REG(i+1)-1; j >= FP_REG(i); j--)
      //   printf ( "%2x", (UInt)m_fpu_state.reg[j]);
      printf ( "  %5s  ", fp_tag_names[fp_get_tag(i)] );
      printf ( "%20.16e\n", fp_get_reg(i) );
   }
   printf("     fctrl:     0x%4x  masked: ", 
          (UInt)m_fpu_state.env[FP_ENV_CTRL] );
   for (i = FP_E_INVAL; i <= FP_E_LOS; i++)
      if (fp_get_controlword_flag(i))
         printf ( "%s ", fp_exception_names[i] );
   printf ( "\n" );

   printf("     fstat:     0x%4x  except:", 
          (UInt)m_fpu_state.env[FP_ENV_STAT] );
   for (i = FP_E_INVAL; i <= FP_E_LOS; i++)
      if (fp_get_statusword_flag(i))
         printf ( "%s ", fp_exception_names[i] );
   printf ( "  top: %d  ", fp_get_tos() );
   printf ( "c3210: %d%d%d%d",
            fp_get_statusword_flag(FP_F_C3),
            fp_get_statusword_flag(FP_F_C2),
            fp_get_statusword_flag(FP_F_C1),
            fp_get_statusword_flag(FP_F_C0) );
   printf ( "  STACKF: %d\n", fp_get_statusword_flag(FP_E_STACKF) );

   printf("      ftag:     0x%4x  ", (UInt)m_fpu_state.env[FP_ENV_TAG] );
   for (i = 7; i >= 0; i--)
      printf ( "%s ", fp_tag_names[fp_get_tag(i)] );
   printf("\n");

   printf("       fip: 0x%8x\n", 
           (((UInt)m_fpu_state.env[FP_ENV_IP+1]) << 16) |
            ((UInt)m_fpu_state.env[FP_ENV_IP]) );
   printf("       fcs:     0x%4x\n", 
           ((UInt)m_fpu_state.env[FP_ENV_CS]) );
   printf("    fopoff: 0x%8x\n", 
           (((UInt)m_fpu_state.env[FP_ENV_OPOFF+1]) << 16) |
            ((UInt)m_fpu_state.env[FP_ENV_OPOFF]) );
   printf("    fopsel:     0x%4x\n", 
           ((UInt)m_fpu_state.env[FP_ENV_OPSEL]) );
}
#endif

/* ---------------------------------------------------------------------
   Implementation of the floating point instruction set.
   ------------------------------------------------------------------ */

/* A pretty nasty kludge.  Arithmetic is done using standard IEEE 
   doubles, which means that programs which rely on the extra accuracy
   supplied by Intel's internal 80-bit format will get different
   results.

   To make exception handling tractable, we assume that the FPU is
   running with all exceptions masked, so we do the "default fixup"
   action for all exceptions.  Fortunately that's fairly simple.

   Support for non-normal numbers (infinities, nans, denorms, etc) is 
   minimal and probably wrong.
*/

typedef
   enum { Fp_Add, Fp_Sub, Fp_Mul, Fp_Div, Fp_SubR, Fp_DivR }
   Fp_Op;

#if DEBUG
char* fp_Op_name ( Fp_Op op )
{
   switch (op) {
      case Fp_Add:  return "add";   case Fp_Sub:  return "sub";
      case Fp_Mul:  return "mul";   case Fp_Div:  return "div";
      case Fp_SubR: return "subr";  case Fp_DivR: return "divr";
      default: panic("fp_Op_name");
   }
   return NULL; /*notreached*/
}
#endif

static
void fp_do_op_ST_ST ( UInt a_src, UInt a_dst, Fp_Op op, Bool pop )
{
   double vd_src, vd_dst;
   IFDB( if (dis) printf("\tf%s%s\t%%st(%d),%%st(%d)\n",
                         fp_Op_name(op), pop?"p":"",
                         a_src, a_dst ); )
   if (!fp_is_empty_tag(fp_get_tag_ST(a_src)) &&
       !fp_is_empty_tag(fp_get_tag_ST(a_dst))) {
      vd_dst = fp_get_reg_ST(a_dst);
      vd_src = fp_get_reg_ST(a_src);
      switch (op) {
         case Fp_Add:  vd_dst = vd_dst + vd_src; break;
         case Fp_Sub:  vd_dst = vd_dst - vd_src; break;
         case Fp_Mul:  vd_dst = vd_dst * vd_src; break;
         case Fp_Div:  vd_dst = vd_dst / vd_src; break;
         case Fp_SubR: vd_dst = vd_src - vd_dst; break;
         case Fp_DivR: vd_dst = vd_src / vd_dst; break;
         default: panic("fp_do_op_ST_ST");
      }      
   } else {
      vd_dst = NAN;
      fp_set_stack_underflow();
   }
   fp_set_reg_ST(a_dst,vd_dst);
   if (pop) (void)fp_pop();
}

static
void fp_do_COM_ST_ST ( UInt a_src, UInt a_dst, UInt nPops )
{
   double vd_src, vd_dst;
   IFDB( if (dis) printf("\tfcom%s\t%%st(%d),%%st(%d)\n",
                         nPops==0 ? "" : (nPops==1 ? "p" : "pp"),
                         a_src, a_dst ); )
   if (!fp_is_empty_tag(fp_get_tag_ST(a_src)) &&
       !fp_is_empty_tag(fp_get_tag_ST(a_dst))) {
      vd_dst = fp_get_reg_ST(a_dst);
      vd_src = fp_get_reg_ST(a_src);
      fp_set_statusword_flags_COM(vd_dst,vd_src);
   } else {
      fp_set_statusword_flags_COM_STACKF();
      fp_set_stack_underflow();
   }
   while (nPops > 0) { 
      (void)fp_pop();
      nPops--;
   }
}

static
void fp_do_op_mem_ST_0 ( UInt a_src,
                         IFDB(Text t_src CC)
                         Fp_Op op, Bool dbl )
{
   double vd_src, vd_dst;
   IFDB( if (dis) printf("\tf%s%c\t%s,%%st(0)\n",
                         fp_Op_name(op), dbl?'D':'F', t_src ); )
   if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
      vd_dst = fp_get_reg_ST(0);
      vd_src = dbl ? getDMem(a_src) : getFMem(a_src);
      switch (op) {
         case Fp_Add:  vd_dst = vd_dst + vd_src; break;
         case Fp_Sub:  vd_dst = vd_dst - vd_src; break;
         case Fp_Mul:  vd_dst = vd_dst * vd_src; break;
         case Fp_Div:  vd_dst = vd_dst / vd_src; break;
         case Fp_SubR: vd_dst = vd_src - vd_dst; break;
         case Fp_DivR: vd_dst = vd_src / vd_dst; break;
         default: panic("fp_do_op_mem_ST_0");
      }      
   } else {
      vd_dst = NAN;
      fp_set_stack_underflow();
   }
   fp_set_reg_ST(0,vd_dst);
}

static
void fp_do_COM_mem_ST_0 ( UInt a_src, 
                          IFDB( Text t_src CC)
                          Bool dbl, Bool pop )
{
   double vd_src, vd_dst;
   IFDB( if (dis) printf("\tfcom%s%c\t%s,%%st(0)\n",
                         pop?"p":"", dbl?'D':'F', t_src ); )
   if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
      vd_dst = fp_get_reg_ST(0);
      vd_src = dbl ? getDMem(a_src) : getFMem(a_src);
      fp_set_statusword_flags_COM(vd_dst,vd_src);
   } else {
      fp_set_statusword_flags_COM_STACKF();
      fp_set_stack_underflow();
   }
   if (pop) (void)fp_pop();
}


Addr do_one_insn_fp ( Addr r_eip, UChar first_opcode )
{
   UChar  modrm;
   UInt   a_addr, a_src, a_dst;
   UInt   opc_aux;
   Bool   isreg;
   Int    vis_addr;
   Int    vis_dst;
   double vd_addr, vd_src, vd_dst;

#  if DEBUG
   Text   t_opc_aux;
   Text   t_addr, t_dst;
   Bool ppFpuState = False;

   if (ppFpuState) {
      printf("\n\nBEFORE\n");
      printFpuState();
      printf("\n");
   }
#  endif

   /* assert that we are running with all exceptions masked */
   assert( (m_fpu_state.env[FP_ENV_CTRL] & 0x3F) == 0x3F );
   /* and the implication is that there are no unmasked exceptions
      reported by the exception status flag. */
   assert( fp_get_statusword_flag(FP_E_SUMMARY) == 0 );

   modrm = *r_eip;

   /* -+-+-+-+-+-+-+-+-+-+-+-+ 0xD8 opcodes +-+-+-+-+-+-+-+ */

   if (first_opcode == 0xD8) {
      if (modrm < 0xC0) {
	/* bits 5,4,3 are an opcode extension, and the modRM also
           specifies an address. */
         opc_aux = regno_from_modRM ( r_eip, 4 IFDB(CC &t_opc_aux) );
         r_eip = amode_from_modRM ( r_eip, 4, &a_addr 
                                    IFDB(CC &t_addr), &isreg );
         assert(!isreg);
         switch (opc_aux) {

            case 0: /* FADD single-real */
               fp_do_op_mem_ST_0 ( a_addr, IFDB(t_addr CC) 
                                   Fp_Add, False );
               break;

            case 1: /* FMUL single-real */
               fp_do_op_mem_ST_0 ( a_addr, IFDB(t_addr CC) 
                                   Fp_Mul, False );
               break;

            case 2: /* FCOM single-real */
               fp_do_COM_mem_ST_0 ( a_addr, IFDB(t_addr CC) 
                                    False, False );
               break;

            case 3: /* FCOMP single-real */
               fp_do_COM_mem_ST_0 ( a_addr, IFDB(t_addr CC) 
                                    False, True );
               break;

            case 4: /* FSUB single-real */
               fp_do_op_mem_ST_0 ( a_addr, IFDB(t_addr CC) 
                                   Fp_Sub, False );
               break;

            case 5: /* FSUBR single-real */
               fp_do_op_mem_ST_0 ( a_addr, IFDB(t_addr CC) 
                                   Fp_SubR, False );
               break;

            case 6: /* FDIV single-real */
               fp_do_op_mem_ST_0 ( a_addr, IFDB(t_addr CC) 
                                   Fp_Div, False );
               break;

            case 7: /* FDIVR single-real */
               fp_do_op_mem_ST_0 ( a_addr, IFDB(t_addr CC) 
                                   Fp_DivR, False );
               break;

            default:
               printf("unhandled opc_aux = 0x%2x\n", opc_aux);
               panic("do_one_insn_fp: first_opcode == 0xD8");
               break;
	 }
      } else {
         /* The entire modRM byte is an opcode extension. */
         r_eip++;
         switch (modrm) {

            case 0xC0 ... 0xC7: /* FADD %st(?),%st(0) */
               fp_do_op_ST_ST ( modrm - 0xC0, 0, Fp_Add, False );
               break;

            case 0xC8 ... 0xCF: /* FMUL %st(?),%st(0) */
               fp_do_op_ST_ST ( modrm - 0xC8, 0, Fp_Mul, False );
               break;

            case 0xD0 ... 0xD7: /* FCOM %st(?),%st(0) */
               fp_do_COM_ST_ST ( modrm - 0xD0, 0, 0 );
               break;

            case 0xD8 ... 0xDF: /* FCOMP %st(?),%st(0) */
               fp_do_COM_ST_ST ( modrm - 0xD8, 0, 1 );
               break;

            case 0xE0 ... 0xE7: /* FSUB %st(?),%st(0) */
               fp_do_op_ST_ST ( modrm - 0xE0, 0, Fp_Sub, False );
               break;

            case 0xE8 ... 0xEF: /* FSUBR %st(?),%st(0) */
               fp_do_op_ST_ST ( modrm - 0xE8, 0, Fp_SubR, False );
               break;

            case 0xF0 ... 0xF7: /* FDIV %st(?),%st(0) */
               fp_do_op_ST_ST ( modrm - 0xF0, 0, Fp_Div, False );
               break;

            case 0xF8 ... 0xFF: /* FDIVR %st(?),%st(0) */
               fp_do_op_ST_ST ( modrm - 0xF8, 0, Fp_DivR, False );
               break;

            default:
               goto unhandled;
	 }
      }
   }

   /* -+-+-+-+-+-+-+-+-+-+-+-+ 0xD9 opcodes +-+-+-+-+-+-+-+ */
   else
   if (first_opcode == 0xD9) {
      if (modrm < 0xC0) {
	/* bits 5,4,3 are an opcode extension, and the modRM also
           specifies an address. */
         opc_aux = regno_from_modRM ( r_eip, 4 IFDB(CC &t_opc_aux) );
         r_eip = amode_from_modRM ( r_eip, 4, &a_addr 
                                    IFDB(CC &t_addr), &isreg );
         assert(!isreg);
         switch (opc_aux) {

            case 0: /* FLD single-real */
               IFDB( if (dis) printf("\tfldF\t%s\n",t_addr); )
               vd_addr = getFMem(a_addr);
               fp_push(vd_addr);
               break;

            case 2: /* FST single-real */
               IFDB( if (dis) printf("\tfstF\t%s\n",t_addr); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = fp_get_reg_ST(0);
               } else {
                  vd_addr = NAN;
                  fp_set_stack_underflow();
               }
               setFMem(a_addr,vd_addr);
               break;

            case 3: /* FSTP single-real */
               IFDB( if (dis) printf("\tfstpF\t%s\n",t_addr); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = fp_pop();
               } else {
                  vd_addr = fp_pop(); /* then throw away result */
                  vd_addr = NAN;
                  fp_set_stack_underflow();
               }
               setFMem(a_addr,vd_addr);
               break;

            case 5: /* FLDCW */
               IFDB( if (dis) printf("\tfldcw\t%s\n",t_addr); )
               m_fpu_state.env[FP_ENV_CTRL] = (UShort)getIMem2(a_addr);
               break;

            case 7: /* FNSTCW */
               IFDB( if (dis) printf("\tfnstcw\t%s\n",t_addr); )
               setIMem2(a_addr,(UInt)m_fpu_state.env[FP_ENV_CTRL]);
               break;

            default:
               printf("unhandled opc_aux = 0x%2x\n", opc_aux);
               panic("do_one_insn_fp: first_opcode == 0xD9");
               break;
	 }
      } else {
         /* The entire modRM byte is an opcode extension. */
         r_eip++;
         switch (modrm) {

            case 0xC0 ... 0xC7: /* FLD %st(?) */
               a_dst = (UInt)modrm - 0xC0;
               IFDB( if (dis) printf("\tfld\t%%st(%d)\n",a_dst); )
               if (!fp_is_empty_tag(fp_get_tag_ST(a_dst)) &&
                   fp_is_empty_tag(fp_get_tag_ST(7))) {
                  vd_dst = fp_get_reg_ST(a_dst);
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
               }
               fp_push(vd_dst);
               break;

            case 0xC8 ... 0xCF: /* FXCH %st(?) */
               a_dst = (UInt)modrm - 0xC8;
               IFDB( if (dis) printf("\tfxch\t%%st(%d)\n",a_dst); )
               if (!fp_is_empty_tag(fp_get_tag_ST(a_dst)) &&
                   !fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_dst = fp_get_reg_ST(a_dst);
                  vd_src = fp_get_reg_ST(0);
               } else {
                  vd_dst = NAN;
                  vd_src = NAN;
                  fp_set_stack_underflow();
               }
               fp_set_reg_ST(a_dst,vd_src);
               fp_set_reg_ST(0,vd_dst);
               break;

            case 0xE0: /* FCHS */
               IFDB( if (dis) printf("\tfchs\n"); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_dst = - fp_get_reg_ST(0);
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
               }
               fp_set_reg_ST(0,vd_dst);
               break;

            case 0xE1: /* FABS */
               IFDB( if (dis) printf("\tfabs\n"); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_dst = fabs(fp_get_reg_ST(0));
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
               }
               fp_set_reg_ST(0,vd_dst);
               break;

            case 0xE5:
               /* An approximation to the correct behaviour */
               IFDB( if (dis) printf("\tfxam\n"); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_dst = fabs(fp_get_reg_ST(0));
                  if (isnan(vd_dst)) 
                     vis_dst = 1; /* C320 = 001 */ 
                  else if (isinf(vd_dst)) 
                     vis_dst = 3; /* C320 = 011 */
                  else if (vd_dst == 0.0 || vd_dst == -0.0)  
                     vis_dst = 4; /* C320 = 100 */
                  else
                     vis_dst = 2; /* C320 = 010 */
                  fp_set_statusword_flag_to(FP_F_C1, 
                                            vd_dst < 0.0 ? 1 : 0);
               } else {
                  vis_dst = 5; /* C320 = 101 */
                  /* no idea if this is right */
                  fp_set_statusword_flag_to(FP_F_C1, 0);
               }
               fp_set_statusword_flag_to(FP_F_C3, (vis_dst >> 2) & 1);
               fp_set_statusword_flag_to(FP_F_C2, (vis_dst >> 1) & 1);
               fp_set_statusword_flag_to(FP_F_C0, vis_dst & 1);
               break;
               
            case 0xE8: /* FLD1 */
               IFDB( t_dst = "1";  )
               vd_dst = 1.0;
               goto do_fld_CONST;
            case 0xEC: /* FLDLG2 */
               IFDB( t_dst = "lg2";  )
               vd_dst = 0.301029995663981143;
               goto do_fld_CONST;
            case 0xED: /* FLDLN2 */
               IFDB( t_dst = "ln2";  )
               vd_dst = 0.69314718055994530942;
               goto do_fld_CONST;
            case 0xEE: /* FLDZ */
               IFDB( t_dst = "z";  )
               vd_dst = 0.0;
               goto do_fld_CONST;
            do_fld_CONST:
               IFDB( if (dis) printf("\tfld%s\n",t_dst); )
               fp_push(vd_dst);
               break;

            case 0xF0: /* F2XM1 */
               IFDB( if (dis) printf("\tf2xm1\n"); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_dst = fp_calc_2xm1(fp_get_reg_ST(0));
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
               }
               fp_set_reg_ST(0,vd_dst);
               break;

            case 0xF1: /* FYL2X */
               IFDB( if (dis) printf("\tfyl2x\n"); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0)) &&
                   !fp_is_empty_tag(fp_get_tag_ST(1))) {
                  vd_dst = fp_calc_yl2x(
                              fp_get_reg_ST(0), fp_get_reg_ST(1));
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
               }
               fp_set_reg_ST(1,vd_dst);
               (void)fp_pop();
               break;

            case 0xF3: /* FPATAN */
               IFDB( if (dis) printf("\tfpatan\n"); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0)) &&
                   !fp_is_empty_tag(fp_get_tag_ST(1))) {
                  vd_dst = atan2(
                              fp_get_reg_ST(1), fp_get_reg_ST(0));
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
               }
               fp_set_reg_ST(1,vd_dst);
               (void)fp_pop();
               break;

            case 0xF8: { /* FPREM */
               /* Very incomplete implementation.  */
               Int qq;
               IFDB( if (dis) printf("\tfprem\n"); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0)) &&
                   !fp_is_empty_tag(fp_get_tag_ST(1))) {
                  fp_calc_fprem( &qq, &vd_dst, 
                                 fp_get_reg_ST(0), fp_get_reg_ST(1) );
                  fp_set_statusword_flag_to(FP_F_C0, (qq & 4) ? 1 : 0);
                  fp_set_statusword_flag_to(FP_F_C1, (qq & 1) ? 1 : 0);
                  fp_set_statusword_flag_to(FP_F_C2, 0); /* reduction complete */
                  fp_set_statusword_flag_to(FP_F_C3, (qq & 2) ? 1 : 0);
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
                  fp_set_statusword_flag_to(FP_F_C1, 0); /* stack underflow */
               }
               fp_set_reg_ST(0,vd_dst);
               break;
            }
            case 0xF9: /* FYL2XP1 */
               IFDB( if (dis) printf("\tfyl2xp1\n"); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0)) &&
                   !fp_is_empty_tag(fp_get_tag_ST(1))) {
                  vd_dst = fp_calc_yl2xp1(
                              fp_get_reg_ST(0), fp_get_reg_ST(1));
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
               }
               fp_set_reg_ST(1,vd_dst);
               (void)fp_pop();
               break;

            case 0xFA: /* FSQRT */
               IFDB( if (dis) printf("\tfsqrt\n"); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_dst = sqrt(fp_get_reg_ST(0));
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
               }
               fp_set_reg_ST(0,vd_dst);
               break;

            case 0xFC: /* FRNDINT */
               IFDB( if (dis) printf("\tfrndint\n"); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_dst = rint(fp_get_reg_ST(0));
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
               }
               fp_set_reg_ST(0,vd_dst);
               break;

            case 0xFD: /* FSCALE */
               IFDB( if (dis) printf("\tfscale\n"); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0)) &&
                   !fp_is_empty_tag(fp_get_tag_ST(1))) {
                  vd_dst = fp_calc_scale(
                              fp_get_reg_ST(0), fp_get_reg_ST(1));
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
               }
               fp_set_reg_ST(0,vd_dst);
               break;

            case 0xFE: /* FSIN */
               IFDB( if (dis) printf("\tfsin\n"); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_dst = sin(fp_get_reg_ST(0));
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
               }
               fp_set_reg_ST(0,vd_dst);
               break;

            case 0xFF: /* FCOS */
               IFDB( if (dis) printf("\tfcos\n"); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_dst = cos(fp_get_reg_ST(0));
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
               }
               fp_set_reg_ST(0,vd_dst);
               break;

            default:
               goto unhandled;
	 }
      }
   }

   /* -+-+-+-+-+-+-+-+-+-+-+-+ 0xDA opcodes +-+-+-+-+-+-+-+ */
   else
   if (first_opcode == 0xDA) {
      if (modrm < 0xC0) {
	/* bits 5,4,3 are an opcode extension, and the modRM also
           specifies an address. */
         opc_aux = regno_from_modRM ( r_eip, 4 IFDB(CC &t_opc_aux) );
         r_eip = amode_from_modRM ( r_eip, 4, &a_addr
                                    IFDB(CC &t_addr), &isreg );
         assert(!isreg);
         switch (opc_aux) {

            case 0: /* FIADD m32int */
               IFDB( if (dis) printf("\tfiaddl\t%s\n",t_addr); )
               vis_addr = getIMem4(a_addr);
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = fp_get_reg_ST(0) + (double)vis_addr;
                  fp_set_reg_ST(0, vd_addr);
                  /* we should set C1 here */
               } else {
                  fp_set_reg_ST(0, NAN);
                  fp_set_stack_underflow();
               }
               break;

            case 1: /* FIMUL m32int */
               IFDB( if (dis) printf("\tfimull\t%s\n",t_addr); )
               vis_addr = getIMem4(a_addr);
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = fp_get_reg_ST(0) * (double)vis_addr;
                  fp_set_reg_ST(0, vd_addr);
                  /* we should set C1 here */
               } else {
                  fp_set_reg_ST(0, NAN);
                  fp_set_stack_underflow();
               }
               break;

            case 2: /* FICOM m32int */
               IFDB( if (dis) printf("\tficoml\t%s\n",t_addr); )
               vis_addr = getIMem4(a_addr);
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_dst = fp_get_reg_ST(0);
                  vd_src = (double)vis_addr;
                  fp_set_statusword_flags_COM(vd_dst,vd_src);
                  /* we should set C1 here */
               } else {
                  fp_set_statusword_flags_COM_STACKF();
                  fp_set_stack_underflow();
               }
               break;

            case 3: /* FICOMP m32int */
               IFDB( if (dis) printf("\tficompl\t%s\n",t_addr); )
               vis_addr = getIMem4(a_addr);
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_dst = fp_get_reg_ST(0);
                  vd_src = (double)vis_addr;
                  fp_set_statusword_flags_COM(vd_dst,vd_src);
                  /* we should set C1 here */
               } else {
                  fp_set_statusword_flags_COM_STACKF();
                  fp_set_stack_underflow();
               }
               (void)fp_pop();
               break;

            case 4: /* FISUB m32int */
               IFDB( if (dis) printf("\tfisubl\t%s\n",t_addr); )
               vis_addr = getIMem4(a_addr);
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = fp_get_reg_ST(0) - (double)vis_addr;
                  fp_set_reg_ST(0, vd_addr);
                  /* we should set C1 here */
               } else {
                  fp_set_reg_ST(0, NAN);
                  fp_set_stack_underflow();
               }
               break;

            case 5: /* FISUBR m32int */
               IFDB( if (dis) printf("\tfisubrl\t%s\n",t_addr); )
               vis_addr = getIMem4(a_addr);
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = (double)vis_addr - fp_get_reg_ST(0);
                  fp_set_reg_ST(0, vd_addr);
                  /* we should set C1 here */
               } else {
                  fp_set_reg_ST(0, NAN);
                  fp_set_stack_underflow();
               }
               break;

            case 6: /* FIDIV m32int */
               IFDB( if (dis) printf("\tfidivl\t%s\n",t_addr); )
               vis_addr = getIMem4(a_addr);
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = fp_get_reg_ST(0) / (double)vis_addr;
                  fp_set_reg_ST(0, vd_addr);
                  /* we should set C1 here */
               } else {
                  fp_set_reg_ST(0, NAN);
                  fp_set_stack_underflow();
               }
               break;

            case 7: /* FIDIVR m32int */
               IFDB( if (dis) printf("\tfidivl\t%s\n",t_addr); )
               vis_addr = getIMem4(a_addr);
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = (double)vis_addr / fp_get_reg_ST(0);
                  fp_set_reg_ST(0, vd_addr);
                  /* we should set C1 here */
               } else {
                  fp_set_reg_ST(0, NAN);
                  fp_set_stack_underflow();
               }
               break;

            default:
               printf("unhandled opc_aux = 0x%2x\n", opc_aux);
               panic("do_one_insn_fp: first_opcode == 0xDA");
               break;
	 }
      } else {
         /* The entire modRM byte is an opcode extension. */
         r_eip++;
         switch (modrm) {

            case 0xE9: /* FUCOMPP %st(0),%st(1) */
               /* seems the wrong way round. */
               fp_do_COM_ST_ST ( 1, 0, 2 );
               break;

            default:
               goto unhandled;
	 }
      }
   }

   /* -+-+-+-+-+-+-+-+-+-+-+-+ 0xDB opcodes +-+-+-+-+-+-+-+ */
   else
   if (first_opcode == 0xDB) {
      if (modrm < 0xC0) {
	/* bits 5,4,3 are an opcode extension, and the modRM also
           specifies an address. */
         opc_aux = regno_from_modRM ( r_eip, 4 IFDB(CC &t_opc_aux) );
         r_eip = amode_from_modRM ( r_eip, 4, &a_addr 
                                    IFDB(CC &t_addr), &isreg );
         assert(!isreg);
         switch (opc_aux) {

            case 0: /* FILD m32int */
               IFDB( if (dis) printf("\tfildl\t%s\n",t_addr); )
               vis_addr = getIMem4(a_addr);
               fp_push ( (double)vis_addr );
               break;

            case 2: /* FIST m32 */
               IFDB( if (dis) printf("\tfistl\t%s\n",t_addr); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = fp_get_reg_ST(0);
                  if (vd_addr <= -2147483648.5 || 
                      vd_addr >= 2147483647.5) 
                     vis_addr = 0x80000000; /* 32-bit int indefinite */
                  else
                     vis_addr = (Int)vd_addr;
               } else {
                  vis_addr = 0x80000000; /* 32-bit indefinite */
                  fp_set_stack_underflow();
               }
               setIMem4(a_addr,vis_addr);
               break;

            case 3: /* FISTP m32 */
               IFDB( if (dis) printf("\tfistpl\t%s\n",t_addr); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = fp_pop();
                  if (vd_addr <= -2147483648.5 || 
                      vd_addr >= 2147483647.5) 
                     vis_addr = 0x80000000; /* 32-bit int indefinite */
                  else
                     vis_addr = (Int)vd_addr;
               } else {
                  vd_addr = fp_pop(); /* then throw away result */
                  vis_addr = 0x80000000; /* 32-bit indefinite */
                  fp_set_stack_underflow();
               }
               setIMem4(a_addr,vis_addr);
               break;

            case 5: /* FLD extended-real */
               IFDB( if (dis) printf("\tfldT\t%s\n",t_addr); )
               vd_addr = getTMem(a_addr);
               fp_push(vd_addr);
               break;

            case 7: /* FSTP extended-real */
               IFDB( if (dis) printf("\tfstpT\t%s\n",t_addr); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = fp_pop();
               } else {
                  vd_addr = fp_pop(); /* then throw away result */
                  vd_addr = NAN;
                  fp_set_stack_underflow();
               }
               setTMem(a_addr,vd_addr);
               break;

            default:
               printf("unhandled opc_aux = 0x%2x\n", opc_aux);
               panic("do_one_insn_fp: first_opcode == 0xDB");
               break;
	 }
      } else {
         /* The entire modRM byte is an opcode extension. */
         r_eip++;
         switch (modrm) {
            default:
               goto unhandled;
	 }
      }
   }

   /* -+-+-+-+-+-+-+-+-+-+-+-+ 0xDC opcodes +-+-+-+-+-+-+-+ */
   else
   if (first_opcode == 0xDC) {
      if (modrm < 0xC0) {
	/* bits 5,4,3 are an opcode extension, and the modRM also
           specifies an address. */
         opc_aux = regno_from_modRM ( r_eip, 4 IFDB(CC &t_opc_aux) );
         r_eip = amode_from_modRM ( r_eip, 4, &a_addr 
                                    IFDB(CC &t_addr), &isreg );
         assert(!isreg);
         switch (opc_aux) {

            case 0: /* FADD double-real */
               fp_do_op_mem_ST_0 ( a_addr, IFDB(t_addr CC) Fp_Add, True );
               break;

            case 1: /* FMUL double-real */
               fp_do_op_mem_ST_0 ( a_addr, IFDB(t_addr CC) Fp_Mul, True );
               break;

            case 2: /* FCOM double-real */
               fp_do_COM_mem_ST_0 ( a_addr, IFDB(t_addr CC) True, False );
               break;

            case 3: /* FCOMP double-real */
               fp_do_COM_mem_ST_0 ( a_addr, IFDB(t_addr CC) True, True );
               break;

            case 4: /* FSUB double-real */
               fp_do_op_mem_ST_0 ( a_addr, IFDB(t_addr CC) Fp_Sub, True );
               break;

            case 5: /* FSUBR double-real */
               fp_do_op_mem_ST_0 ( a_addr, IFDB(t_addr CC) Fp_SubR, True );
               break;

            case 6: /* FDIV double-real */
               fp_do_op_mem_ST_0 ( a_addr, IFDB(t_addr CC) Fp_Div, True );
               break;

            case 7: /* FDIVR double-real */
               fp_do_op_mem_ST_0 ( a_addr, IFDB(t_addr CC) Fp_DivR, True );
               break;

            default:
               printf("unhandled opc_aux = 0x%2x\n", opc_aux);
               panic("do_one_insn_fp: first_opcode == 0xDC");
               break;
	 }
      } else {
         /* The entire modRM byte is an opcode extension. */
         r_eip++;
         switch (modrm) {

            case 0xC0 ... 0xC7: /* FADD %st(0),%st(?) */
               fp_do_op_ST_ST ( 0, modrm - 0xC0, Fp_Add, False );
               break;

            case 0xC8 ... 0xCF: /* FMUL %st(0),%st(?) */
               fp_do_op_ST_ST ( 0, modrm - 0xC8, Fp_Mul, False );
               break;

            case 0xE0 ... 0xE7: /* FSUBR %st(0),%st(?) */
               fp_do_op_ST_ST ( 0, modrm - 0xE0, Fp_SubR, False );
               break;

            case 0xE8 ... 0xEF: /* FSUB %st(0),%st(?) */
               fp_do_op_ST_ST ( 0, modrm - 0xE8, Fp_Sub, False );
               break;

            case 0xF8 ... 0xFF: /* FDIV %st(0),%st(?) */
               fp_do_op_ST_ST ( 0, modrm - 0xF8, Fp_Div, False );
               break;

            default:
               goto unhandled;
	 }
      }
   }

   /* -+-+-+-+-+-+-+-+-+-+-+-+ 0xDD opcodes +-+-+-+-+-+-+-+ */
   else
   if (first_opcode == 0xDD) {
      if (modrm < 0xC0) {
	/* bits 5,4,3 are an opcode extension, and the modRM also
           specifies an address. */
         opc_aux = regno_from_modRM ( r_eip, 4 IFDB(CC &t_opc_aux) );
         r_eip = amode_from_modRM ( r_eip, 4, &a_addr 
                                    IFDB(CC &t_addr), &isreg );
         assert(!isreg);
         switch (opc_aux) {

            case 0: /* FLD double-real */
               IFDB( if (dis) printf("\tfldD\t%s\n",t_addr); )
               vd_addr = getDMem(a_addr);
               fp_push(vd_addr);
               break;

            case 2: /* FST double-real */
               IFDB( if (dis) printf("\tfstD\t%s\n",t_addr); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = fp_get_reg_ST(0);
               } else {
                  vd_addr = NAN;
                  fp_set_stack_underflow();
               }
               setDMem(a_addr,vd_addr);
               break;

            case 3: /* FSTP double-real */
               IFDB( if (dis) printf("\tfstpD\t%s\n",t_addr); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = fp_pop();
               } else {
                  vd_addr = fp_pop(); /* then throw away result */
                  vd_addr = NAN;
                  fp_set_stack_underflow();
               }
               setDMem(a_addr,vd_addr);
               break;
            default:
               printf("unhandled opc_aux = 0x%2x\n", opc_aux);
               panic("do_one_insn_fp: first_opcode == 0xDD");
               break;
	 }
      } else {
         /* The entire modRM byte is an opcode extension. */
         r_eip++;
         switch (modrm) {

            case 0xC0 ... 0xC7: /* FFREE %st(?) */
               a_dst = (UInt)modrm - 0xC0;
               IFDB( if (dis) printf("\tffree\t%%st(%d)\n", a_dst); )
               fp_set_tag_ST( a_dst, FP_TAG_EMPTY );
               break;

            case 0xD0 ... 0xD7: /* FST %st(0),%st(?) */
               a_dst = (UInt)modrm - 0xD0;
               IFDB( if (dis) printf("\tfst\t%%st(0),%%st(%d)\n",
                                     a_dst); )
               if ( /* don't check the destination tag */
                    !fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_dst = fp_get_reg_ST(0);
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
               }
               fp_set_reg_ST(a_dst,vd_dst);
               break;

            case 0xD8 ... 0xDF: /* FSTP %st(0),%st(?) */
               a_dst = (UInt)modrm - 0xD8;
               IFDB( if (dis) printf("\tfstp\t%%st(0),%%st(%d)\n",
                                     a_dst); )
               if ( /* don't check the destination tag */
                    !fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_dst = fp_get_reg_ST(0);
               } else {
                  vd_dst = NAN;
                  fp_set_stack_underflow();
               }
               fp_set_reg_ST(a_dst,vd_dst);
               (void)fp_pop();
               break;

            case 0xE0 ... 0xE7: /* FUCOM %st(0),%st(?) */
               a_src = (UInt)modrm - 0xE0;
               IFDB( if (dis) printf("\tfucom\t%%st(0),%%st(%d)\n",
                                     a_src); )
               if (!fp_is_empty_tag(fp_get_tag_ST(a_src)) &&
                   !fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_src = fp_get_reg_ST(a_src);
                  vd_dst = fp_get_reg_ST(0);
                  fp_set_statusword_flags_COM(vd_dst,vd_src);
               } else {
                  fp_set_statusword_flags_COM_STACKF();
                  fp_set_stack_underflow();
               }
               break;

            case 0xE8 ... 0xEF: /* FUCOMP %st(0),%st(?) */
               a_src = (UInt)modrm - 0xE8;
               IFDB( if (dis) printf("\tfucomp\t%%st(0),%%st(%d)\n",
                                     a_src); )
               if (!fp_is_empty_tag(fp_get_tag_ST(a_src)) &&
                   !fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_src = fp_get_reg_ST(a_src);
                  vd_dst = fp_get_reg_ST(0);
                  fp_set_statusword_flags_COM(vd_dst,vd_src);
               } else {
                  fp_set_statusword_flags_COM_STACKF();
                  fp_set_stack_underflow();
               }
               (void)fp_pop();
               break;

            default:
               goto unhandled;
	 }
      }
   }

   /* -+-+-+-+-+-+-+-+-+-+-+-+ 0xDE opcodes +-+-+-+-+-+-+-+ */
   else
   if (first_opcode == 0xDE) {
      if (modrm < 0xC0) {
	/* bits 5,4,3 are an opcode extension, and the modRM also
           specifies an address. */
         opc_aux = regno_from_modRM ( r_eip, 4 IFDB(CC &t_opc_aux) );
         r_eip = amode_from_modRM ( r_eip, 4, &a_addr 
                                    IFDB(CC &t_addr), &isreg );
         assert(!isreg);
         switch (opc_aux) {
            default:
               printf("unhandled opc_aux = 0x%2x\n", opc_aux);
               panic("do_one_insn_fp: first_opcode == 0xDE");
               break;
	 }
      } else {
         /* The entire modRM byte is an opcode extension. */
         r_eip++;
         switch (modrm) {

            case 0xC0 ... 0xC7: /* FADDP %st(0),%st(?) */
               fp_do_op_ST_ST ( 0, modrm - 0xC0, Fp_Add, True );
               break;

            case 0xC8 ... 0xCF: /* FMULP %st(0),%st(?) */
               fp_do_op_ST_ST ( 0, modrm - 0xC8, Fp_Mul, True );
               break;

            case 0xD9: /* FCOMPP %st(0),%st(1) */
               /* seems the wrong way round. */
               fp_do_COM_ST_ST ( 1, 0, 2 );
               break;

            case 0xE0 ... 0xE7: /* FSUBRP %st(0),%st(?) */
               fp_do_op_ST_ST ( 0, modrm - 0xE0, Fp_SubR, True );
               break;

            case 0xE8 ... 0xEF: /* FSUBP %st(0),%st(?) */
               fp_do_op_ST_ST ( 0, modrm - 0xE8, Fp_Sub, True );
               break;

            case 0xF0 ... 0xF7: /* FDIVRP %st(0),%st(?) */
               fp_do_op_ST_ST ( 0, modrm - 0xF0, Fp_DivR, True );
               break;

            case 0xF8 ... 0xFF: /* FDIVP %st(0),%st(?) */
               fp_do_op_ST_ST ( 0, modrm - 0xF8, Fp_Div, True );
               break;

            default:
               goto unhandled;
	 }
      }
   }

   /* -+-+-+-+-+-+-+-+-+-+-+-+ 0xDF opcodes +-+-+-+-+-+-+-+ */
   else
   if (first_opcode == 0xDF) {
      if (modrm < 0xC0) {
	/* bits 5,4,3 are an opcode extension, and the modRM also
           specifies an address. */
         opc_aux = regno_from_modRM ( r_eip, 4 IFDB(CC &t_opc_aux) );
         r_eip = amode_from_modRM ( r_eip, 4, &a_addr 
                                    IFDB(CC &t_addr), &isreg );
         assert(!isreg);
         switch (opc_aux) {

            case 0: /* FILD m16int */
               IFDB( if (dis) printf("\tfildw\t%s\n",t_addr); )
               vis_addr = extend_s_16to32(getIMem2(a_addr));
               fp_push ( (double) vis_addr );
               break;

            case 3: /* FISTP m16 */
               IFDB( if (dis) printf("\tfistpw\t%s\n",t_addr); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = fp_pop();
                  if (vd_addr <= -32768.50 || 
                      vd_addr >= 32767.50) 
                     vis_addr = 0x00008000; /* 16-bit int indefinite */
                  else
                     vis_addr = (Short)vd_addr;
               } else {
                  vd_addr = fp_pop(); /* then throw away result */
                  vis_addr = 0x00008000; /* 32-bit indefinite */
                  fp_set_stack_underflow();
               }
               setIMem2(a_addr,vis_addr);
               break;

            case 5: { /* FILD m64int */
               ULong vis_addr64;
               IFDB( if (dis) printf("\tfildq\t%s\n",t_addr); )
               vis_addr   = getIMem4(a_addr+4);
               vis_addr64 = ((ULong)vis_addr) << 32;
               vis_addr   = getIMem4(a_addr);
               vis_addr64 += (ULong)vis_addr;
               fp_push ( (double) ((Long)vis_addr64) );
               break;
            }

            case 7: { /* FISTP m64int */
               ULong vis_addr64;
               IFDB( if (dis) printf("\tfistpq\t%s\n",t_addr); )
               if (!fp_is_empty_tag(fp_get_tag_ST(0))) {
                  vd_addr = fp_pop();
                  if (vd_addr <= -9223372036854775808.5 ||
                      vd_addr >= 9223372036854775807.5) 
                     vis_addr64 = 0x8000000000000000LL;
                         /* 64-bit int indefinite */
                  else
                     vis_addr64 = (Long)vd_addr;
               } else {
                  vd_addr = fp_pop(); /* then throw away result */
                  vis_addr64 = 0x8000000000000000LL; /* 64-bit indefinite */
                  fp_set_stack_underflow();
               }
               setIMem4(a_addr,vis_addr64 & 0xFFFFFFFFLL);
               setIMem4(a_addr+4, (((Long)vis_addr64) >> 32) 
                                   & 0xFFFFFFFFLL);
               break;
            }

            default:
               printf("unhandled opc_aux = 0x%2x\n", opc_aux);
               panic("do_one_insn_fp: first_opcode == 0xDF");
               break;
	 }
      } else {
         /* The entire modRM byte is an opcode extension. */
         r_eip++;
         switch (modrm) {

            case 0xE0: /* FNSTSW %ax */
               IFDB( if (dis) printf("\tfnstsw\t%%ax\n"); )
               setIReg2(R_EAX, (UInt)m_fpu_state.env[FP_ENV_STAT]);
               break;

            default:
               goto unhandled;
	 }
      }
   }

   /* -+-+-+-+-+-+-+-+-+-+-+-+ Unhandled ESC opcode +-+-+-+ */
   else goto unhandled;

#  if DEBUG
   if (ppFpuState) {
      printf("\nAFTER\n");
      printFpuState();
      printf("\n");
   }
#  endif

   return r_eip;

  unhandled:
   hd_message(Hd_DebugMsg,
              "first opcode = 0x%x, modRM = 0x%x",
              (UInt)first_opcode, (UInt)modrm );
   panic("do_one_insn_fp: unhandled first_opcode/modrm combination");
   assert(0);
}

/*--------------------------------------------------------------------*/
/*--- end                                                 hd_fpu.c ---*/
/*--------------------------------------------------------------------*/