| |
| /*--------------------------------------------------------------------*/ |
| /*--- 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 ---*/ |
| /*--------------------------------------------------------------------*/ |