diff options
author | j_mayer <j_mayer@c046a42c-6fe2-441c-8c8c-71466251a162> | 2007-10-27 17:54:30 +0000 |
---|---|---|
committer | j_mayer <j_mayer@c046a42c-6fe2-441c-8c8c-71466251a162> | 2007-10-27 17:54:30 +0000 |
commit | 7c58044c0ab79f11604f71aa04b4691baacef886 (patch) | |
tree | b8a4f5b7e4ab2ada25ced61c0bd05a5d6a49e3f2 /target-ppc/op_helper.c | |
parent | a32ff1ad95c4f09420c4b5e19dfd86ded0d2bc79 (diff) |
Fix PowerPC FPSCR update and floating-point exception generation
in most useful cases.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@3458 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'target-ppc/op_helper.c')
-rw-r--r-- | target-ppc/op_helper.c | 919 |
1 files changed, 740 insertions, 179 deletions
diff --git a/target-ppc/op_helper.c b/target-ppc/op_helper.c index 06a5a561ca..8182773564 100644 --- a/target-ppc/op_helper.c +++ b/target-ppc/op_helper.c @@ -51,14 +51,6 @@ void do_raise_exception_err (uint32_t exception, int error_code) #if 0 printf("Raise exception %3x code : %d\n", exception, error_code); #endif - switch (exception) { - case POWERPC_EXCP_PROGRAM: - if (error_code == POWERPC_EXCP_FP && msr_fe0 == 0 && msr_fe1 == 0) - return; - break; - default: - break; - } env->exception_index = exception; env->error_code = error_code; cpu_loop_exit(); @@ -107,77 +99,6 @@ void do_store_pri (int prio) } #endif -void do_load_fpscr (void) -{ - /* The 32 MSB of the target fpr are undefined. - * They'll be zero... - */ - union { - float64 d; - struct { - uint32_t u[2]; - } s; - } u; - int i; - -#if defined(WORDS_BIGENDIAN) -#define WORD0 0 -#define WORD1 1 -#else -#define WORD0 1 -#define WORD1 0 -#endif - u.s.u[WORD0] = 0; - u.s.u[WORD1] = 0; - for (i = 0; i < 8; i++) - u.s.u[WORD1] |= env->fpscr[i] << (4 * i); - FT0 = u.d; -} - -void do_store_fpscr (uint32_t mask) -{ - /* - * We use only the 32 LSB of the incoming fpr - */ - union { - double d; - struct { - uint32_t u[2]; - } s; - } u; - int i, rnd_type; - - u.d = FT0; - if (mask & 0x80) - env->fpscr[0] = (env->fpscr[0] & 0x9) | ((u.s.u[WORD1] >> 28) & ~0x9); - for (i = 1; i < 7; i++) { - if (mask & (1 << (7 - i))) - env->fpscr[i] = (u.s.u[WORD1] >> (4 * (7 - i))) & 0xF; - } - /* TODO: update FEX & VX */ - /* Set rounding mode */ - switch (env->fpscr[0] & 0x3) { - case 0: - /* Best approximation (round to nearest) */ - rnd_type = float_round_nearest_even; - break; - case 1: - /* Smaller magnitude (round toward zero) */ - rnd_type = float_round_to_zero; - break; - case 2: - /* Round toward +infinite */ - rnd_type = float_round_up; - break; - default: - case 3: - /* Round toward -infinite */ - rnd_type = float_round_down; - break; - } - set_float_rounding_mode(rnd_type, &env->fp_status); -} - target_ulong ppc_load_dump_spr (int sprn) { if (loglevel != 0) { @@ -553,6 +474,538 @@ void do_popcntb_64 (void) /*****************************************************************************/ /* Floating point operations helpers */ +static inline int fpisneg (float64 f) +{ + union { + float64 f; + uint64_t u; + } u; + + u.f = f; + + return u.u >> 63 != 0; +} + +static inline int isden (float f) +{ + union { + float64 f; + uint64_t u; + } u; + + u.f = f; + + return ((u.u >> 52) & 0x7FF) == 0; +} + +static inline int iszero (float64 f) +{ + union { + float64 f; + uint64_t u; + } u; + + u.f = f; + + return (u.u & ~0x8000000000000000ULL) == 0; +} + +static inline int isinfinity (float64 f) +{ + union { + float64 f; + uint64_t u; + } u; + + u.f = f; + + return ((u.u >> 51) & 0x3FF) == 0x3FF && + (u.u & 0x000FFFFFFFFFFFFFULL) == 0; +} + +void do_compute_fprf (int set_fprf) +{ + int isneg; + + isneg = fpisneg(FT0); + if (unlikely(float64_is_nan(FT0))) { + if (float64_is_signaling_nan(FT0)) { + /* Signaling NaN: flags are undefined */ + T0 = 0x00; + } else { + /* Quiet NaN */ + T0 = 0x11; + } + } else if (unlikely(isinfinity(FT0))) { + /* +/- infinity */ + if (isneg) + T0 = 0x09; + else + T0 = 0x05; + } else { + if (iszero(FT0)) { + /* +/- zero */ + if (isneg) + T0 = 0x12; + else + T0 = 0x02; + } else { + if (isden(FT0)) { + /* Denormalized numbers */ + T0 = 0x10; + } else { + /* Normalized numbers */ + T0 = 0x00; + } + if (isneg) { + T0 |= 0x08; + } else { + T0 |= 0x04; + } + } + } + if (set_fprf) { + /* We update FPSCR_FPRF */ + env->fpscr &= ~(0x1F << FPSCR_FPRF); + env->fpscr |= T0 << FPSCR_FPRF; + } + /* We just need fpcc to update Rc1 */ + T0 &= 0xF; +} + +/* Floating-point invalid operations exception */ +static always_inline void fload_invalid_op_excp (int op) +{ + int ve; + + ve = fpscr_ve; + if (op & POWERPC_EXCP_FP_VXSNAN) { + /* Operation on signaling NaN */ + env->fpscr |= 1 << FPSCR_VXSNAN; + } + if (op & POWERPC_EXCP_FP_VXSOFT) { + /* Software-defined condition */ + env->fpscr |= 1 << FPSCR_VXSOFT; + } + switch (op & ~(POWERPC_EXCP_FP_VXSOFT | POWERPC_EXCP_FP_VXSNAN)) { + case POWERPC_EXCP_FP_VXISI: + /* Magnitude subtraction of infinities */ + env->fpscr |= 1 << FPSCR_VXISI; + goto update_arith; + case POWERPC_EXCP_FP_VXIDI: + /* Division of infinity by infinity */ + env->fpscr |= 1 << FPSCR_VXIDI; + goto update_arith; + case POWERPC_EXCP_FP_VXZDZ: + /* Division of zero by zero */ + env->fpscr |= 1 << FPSCR_VXZDZ; + goto update_arith; + case POWERPC_EXCP_FP_VXIMZ: + /* Multiplication of zero by infinity */ + env->fpscr |= 1 << FPSCR_VXIMZ; + goto update_arith; + case POWERPC_EXCP_FP_VXVC: + /* Ordered comparison of NaN */ + env->fpscr |= 1 << FPSCR_VXVC; + env->fpscr &= ~(0xF << FPSCR_FPCC); + env->fpscr |= 0x11 << FPSCR_FPCC; + /* We must update the target FPR before raising the exception */ + if (ve != 0) { + env->exception_index = POWERPC_EXCP_PROGRAM; + env->error_code = POWERPC_EXCP_FP | POWERPC_EXCP_FP_VXVC; + /* Update the floating-point enabled exception summary */ + env->fpscr |= 1 << FPSCR_FEX; + /* Exception is differed */ + ve = 0; + } + break; + case POWERPC_EXCP_FP_VXSQRT: + /* Square root of a negative number */ + env->fpscr |= 1 << FPSCR_VXSQRT; + update_arith: + env->fpscr &= ~((1 << FPSCR_FR) | (1 << FPSCR_FI)); + if (ve == 0) { + /* Set the result to quiet NaN */ + FT0 = (uint64_t)-1; + env->fpscr &= ~(0xF << FPSCR_FPCC); + env->fpscr |= 0x11 << FPSCR_FPCC; + } + break; + case POWERPC_EXCP_FP_VXCVI: + /* Invalid conversion */ + env->fpscr |= 1 << FPSCR_VXCVI; + env->fpscr &= ~((1 << FPSCR_FR) | (1 << FPSCR_FI)); + if (ve == 0) { + /* Set the result to quiet NaN */ + FT0 = (uint64_t)-1; + env->fpscr &= ~(0xF << FPSCR_FPCC); + env->fpscr |= 0x11 << FPSCR_FPCC; + } + break; + } + /* Update the floating-point invalid operation summary */ + env->fpscr |= 1 << FPSCR_VX; + /* Update the floating-point exception summary */ + env->fpscr |= 1 << FPSCR_FX; + if (ve != 0) { + /* Update the floating-point enabled exception summary */ + env->fpscr |= 1 << FPSCR_FEX; + if (msr_fe0 != 0 || msr_fe1 != 0) + do_raise_exception_err(POWERPC_EXCP_PROGRAM, POWERPC_EXCP_FP | op); + } +} + +static always_inline void float_zero_divide_excp (void) +{ + union { + float64 f; + uint64_t u; + } u0, u1; + + + env->fpscr |= 1 << FPSCR_ZX; + env->fpscr &= ~((1 << FPSCR_FR) | (1 << FPSCR_FI)); + /* Update the floating-point exception summary */ + env->fpscr |= 1 << FPSCR_FX; + if (fpscr_ze != 0) { + /* Update the floating-point enabled exception summary */ + env->fpscr |= 1 << FPSCR_FEX; + if (msr_fe0 != 0 || msr_fe1 != 0) { + do_raise_exception_err(POWERPC_EXCP_PROGRAM, + POWERPC_EXCP_FP | POWERPC_EXCP_FP_ZX); + } + } else { + /* Set the result to infinity */ + u0.f = FT0; + u1.f = FT1; + u0.u = ((u0.u ^ u1.u) & 0x8000000000000000ULL); + u0.u |= 0x3FFULL << 51; + FT0 = u0.f; + } +} + +static always_inline void float_overflow_excp (void) +{ + env->fpscr |= 1 << FPSCR_OX; + /* Update the floating-point exception summary */ + env->fpscr |= 1 << FPSCR_FX; + if (fpscr_oe != 0) { + /* XXX: should adjust the result */ + /* Update the floating-point enabled exception summary */ + env->fpscr |= 1 << FPSCR_FEX; + /* We must update the target FPR before raising the exception */ + env->exception_index = POWERPC_EXCP_PROGRAM; + env->error_code = POWERPC_EXCP_FP | POWERPC_EXCP_FP_OX; + } else { + env->fpscr |= 1 << FPSCR_XX; + env->fpscr |= 1 << FPSCR_FI; + } +} + +static always_inline void float_underflow_excp (void) +{ + env->fpscr |= 1 << FPSCR_UX; + /* Update the floating-point exception summary */ + env->fpscr |= 1 << FPSCR_FX; + if (fpscr_ue != 0) { + /* XXX: should adjust the result */ + /* Update the floating-point enabled exception summary */ + env->fpscr |= 1 << FPSCR_FEX; + /* We must update the target FPR before raising the exception */ + env->exception_index = POWERPC_EXCP_PROGRAM; + env->error_code = POWERPC_EXCP_FP | POWERPC_EXCP_FP_UX; + } +} + +static always_inline void float_inexact_excp (void) +{ + env->fpscr |= 1 << FPSCR_XX; + /* Update the floating-point exception summary */ + env->fpscr |= 1 << FPSCR_FX; + if (fpscr_xe != 0) { + /* Update the floating-point enabled exception summary */ + env->fpscr |= 1 << FPSCR_FEX; + /* We must update the target FPR before raising the exception */ + env->exception_index = POWERPC_EXCP_PROGRAM; + env->error_code = POWERPC_EXCP_FP | POWERPC_EXCP_FP_XX; + } +} + +static always_inline void fpscr_set_rounding_mode (void) +{ + int rnd_type; + + /* Set rounding mode */ + switch (fpscr_rn) { + case 0: + /* Best approximation (round to nearest) */ + rnd_type = float_round_nearest_even; + break; + case 1: + /* Smaller magnitude (round toward zero) */ + rnd_type = float_round_to_zero; + break; + case 2: + /* Round toward +infinite */ + rnd_type = float_round_up; + break; + default: + case 3: + /* Round toward -infinite */ + rnd_type = float_round_down; + break; + } + set_float_rounding_mode(rnd_type, &env->fp_status); +} + +void do_fpscr_setbit (int bit) +{ + int prev; + + prev = (env->fpscr >> bit) & 1; + env->fpscr |= 1 << bit; + if (prev == 0) { + switch (bit) { + case FPSCR_VX: + env->fpscr |= 1 << FPSCR_FX; + if (fpscr_ve) + goto raise_ve; + case FPSCR_OX: + env->fpscr |= 1 << FPSCR_FX; + if (fpscr_oe) + goto raise_oe; + break; + case FPSCR_UX: + env->fpscr |= 1 << FPSCR_FX; + if (fpscr_ue) + goto raise_ue; + break; + case FPSCR_ZX: + env->fpscr |= 1 << FPSCR_FX; + if (fpscr_ze) + goto raise_ze; + break; + case FPSCR_XX: + env->fpscr |= 1 << FPSCR_FX; + if (fpscr_xe) + goto raise_xe; + break; + case FPSCR_VXSNAN: + case FPSCR_VXISI: + case FPSCR_VXIDI: + case FPSCR_VXZDZ: + case FPSCR_VXIMZ: + case FPSCR_VXVC: + case FPSCR_VXSOFT: + case FPSCR_VXSQRT: + case FPSCR_VXCVI: + env->fpscr |= 1 << FPSCR_VX; + env->fpscr |= 1 << FPSCR_FX; + if (fpscr_ve != 0) + goto raise_ve; + break; + case FPSCR_VE: + if (fpscr_vx != 0) { + raise_ve: + env->error_code = POWERPC_EXCP_FP; + if (fpscr_vxsnan) + env->error_code |= POWERPC_EXCP_FP_VXSNAN; + if (fpscr_vxisi) + env->error_code |= POWERPC_EXCP_FP_VXISI; + if (fpscr_vxidi) + env->error_code |= POWERPC_EXCP_FP_VXIDI; + if (fpscr_vxzdz) + env->error_code |= POWERPC_EXCP_FP_VXZDZ; + if (fpscr_vximz) + env->error_code |= POWERPC_EXCP_FP_VXIMZ; + if (fpscr_vxvc) + env->error_code |= POWERPC_EXCP_FP_VXVC; + if (fpscr_vxsoft) + env->error_code |= POWERPC_EXCP_FP_VXSOFT; + if (fpscr_vxsqrt) + env->error_code |= POWERPC_EXCP_FP_VXSQRT; + if (fpscr_vxcvi) + env->error_code |= POWERPC_EXCP_FP_VXCVI; + goto raise_excp; + } + break; + case FPSCR_OE: + if (fpscr_ox != 0) { + raise_oe: + env->error_code = POWERPC_EXCP_FP | POWERPC_EXCP_FP_OX; + goto raise_excp; + } + break; + case FPSCR_UE: + if (fpscr_ux != 0) { + raise_ue: + env->error_code = POWERPC_EXCP_FP | POWERPC_EXCP_FP_UX; + goto raise_excp; + } + break; + case FPSCR_ZE: + if (fpscr_zx != 0) { + raise_ze: + env->error_code = POWERPC_EXCP_FP | POWERPC_EXCP_FP_ZX; + goto raise_excp; + } + break; + case FPSCR_XE: + if (fpscr_xx != 0) { + raise_xe: + env->error_code = POWERPC_EXCP_FP | POWERPC_EXCP_FP_XX; + goto raise_excp; + } + break; + case FPSCR_RN1: + case FPSCR_RN: + fpscr_set_rounding_mode(); + break; + default: + break; + raise_excp: + /* Update the floating-point enabled exception summary */ + env->fpscr |= 1 << FPSCR_FEX; + /* We have to update Rc1 before raising the exception */ + env->exception_index = POWERPC_EXCP_PROGRAM; + break; + } + } +} + +#if defined(WORDS_BIGENDIAN) +#define WORD0 0 +#define WORD1 1 +#else +#define WORD0 1 +#define WORD1 0 +#endif +void do_store_fpscr (uint32_t mask) +{ + /* + * We use only the 32 LSB of the incoming fpr + */ + union { + double d; + struct { + uint32_t u[2]; + } s; + } u; + uint32_t prev, new; + int i; + + u.d = FT0; + prev = env->fpscr; + new = u.s.u[WORD1]; + new &= ~0x90000000; + new |= prev & 0x90000000; + for (i = 0; i < 7; i++) { + if (mask & (1 << i)) { + env->fpscr &= ~(0xF << (4 * i)); + env->fpscr |= new & (0xF << (4 * i)); + } + } + /* Update VX and FEX */ + if (fpscr_ix != 0) + env->fpscr |= 1 << FPSCR_VX; + if ((fpscr_ex & fpscr_eex) != 0) { + env->fpscr |= 1 << FPSCR_FEX; + env->exception_index = POWERPC_EXCP_PROGRAM; + /* XXX: we should compute it properly */ + env->error_code = POWERPC_EXCP_FP; + } + fpscr_set_rounding_mode(); +} +#undef WORD0 +#undef WORD1 + +#ifdef CONFIG_SOFTFLOAT +void do_float_check_status (void) +{ + if (env->exception_index == POWERPC_EXCP_PROGRAM && + (env->error_code & POWERPC_EXCP_FP)) { + /* Differred floating-point exception after target FPR update */ + if (msr_fe0 != 0 || msr_fe1 != 0) + do_raise_exception_err(env->exception_index, env->error_code); + } else if (env->fp_status.float_exception_flags & float_flag_overflow) { + float_overflow_excp(); + } else if (env->fp_status.float_exception_flags & float_flag_underflow) { + float_underflow_excp(); + } else if (env->fp_status.float_exception_flags & float_flag_inexact) { + float_inexact_excp(); + } +} +#endif + +#if USE_PRECISE_EMULATION +void do_fadd (void) +{ + if (unlikely(float64_is_signaling_nan(FT0) || + float64_is_signaling_nan(FT1))) { + /* sNaN addition */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN); + } else if (likely(isfinite(FT0) || isfinite(FT1) || + fpisneg(FT0) == fpisneg(FT1))) { + FT0 = float64_add(FT0, FT1, &env->fp_status); + } else { + /* Magnitude subtraction of infinities */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXISI); + } +} + +void do_fsub (void) +{ + if (unlikely(float64_is_signaling_nan(FT0) || + float64_is_signaling_nan(FT1))) { + /* sNaN subtraction */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN); + } else if (likely(isfinite(FT0) || isfinite(FT1) || + fpisneg(FT0) != fpisneg(FT1))) { + FT0 = float64_sub(FT0, FT1, &env->fp_status); + } else { + /* Magnitude subtraction of infinities */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXISI); + } +} + +void do_fmul (void) +{ + if (unlikely(float64_is_signaling_nan(FT0) || + float64_is_signaling_nan(FT1))) { + /* sNaN multiplication */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN); + } else if (unlikely((ifinf(FT0) && iszero(FT1)) || + (inzero(FT0) && isinfinity(FT1)))) { + /* Multiplication of zero by infinity */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXIMZ); + } else { + FT0 = float64_mul(FT0, FT1, &env->fp_status); + } +} + +void do_fdiv (void) +{ + if (unlikely(float64_is_signaling_nan(FT0) || + float64_is_signaling_nan(FT1))) { + /* sNaN division */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN); + } else if (unlikely(isinfinity(FT0) && isinfinity(FT1))) { + /* Division of infinity by infinity */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXIDI); + } else if (unlikely(iszero(FT1))) { + if (iszero(FT0)) { + /* Division of zero by zero */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXZDZ); + } else { + /* Division by zero */ + float_zero_divide_excp(); + } + } else { + FT0 = float64_div(FT0, FT1, &env->fp_status); + } +} +#endif /* USE_PRECISE_EMULATION */ + void do_fctiw (void) { union { @@ -560,14 +1013,22 @@ void do_fctiw (void) uint64_t i; } p; - p.i = float64_to_int32(FT0, &env->fp_status); + if (unlikely(float64_is_signaling_nan(FT0))) { + /* sNaN conversion */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN | POWERPC_EXCP_FP_VXCVI); + } else if (unlikely(float64_is_nan(FT0) || isinfinity(FT0))) { + /* qNan / infinity conversion */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXCVI); + } else { + p.i = float64_to_int32(FT0, &env->fp_status); #if USE_PRECISE_EMULATION - /* XXX: higher bits are not supposed to be significant. - * to make tests easier, return the same as a real PowerPC 750 (aka G3) - */ - p.i |= 0xFFF80000ULL << 32; + /* XXX: higher bits are not supposed to be significant. + * to make tests easier, return the same as a real PowerPC 750 + */ + p.i |= 0xFFF80000ULL << 32; #endif - FT0 = p.d; + FT0 = p.d; + } } void do_fctiwz (void) @@ -577,14 +1038,22 @@ void do_fctiwz (void) uint64_t i; } p; - p.i = float64_to_int32_round_to_zero(FT0, &env->fp_status); + if (unlikely(float64_is_signaling_nan(FT0))) { + /* sNaN conversion */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN | POWERPC_EXCP_FP_VXCVI); + } else if (unlikely(float64_is_nan(FT0) || isinfinity(FT0))) { + /* qNan / infinity conversion */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXCVI); + } else { + p.i = float64_to_int32_round_to_zero(FT0, &env->fp_status); #if USE_PRECISE_EMULATION - /* XXX: higher bits are not supposed to be significant. - * to make tests easier, return the same as a real PowerPC 750 (aka G3) - */ - p.i |= 0xFFF80000ULL << 32; + /* XXX: higher bits are not supposed to be significant. + * to make tests easier, return the same as a real PowerPC 750 + */ + p.i |= 0xFFF80000ULL << 32; #endif - FT0 = p.d; + FT0 = p.d; + } } #if defined(TARGET_PPC64) @@ -606,8 +1075,16 @@ void do_fctid (void) uint64_t i; } p; - p.i = float64_to_int64(FT0, &env->fp_status); - FT0 = p.d; + if (unlikely(float64_is_signaling_nan(FT0))) { + /* sNaN conversion */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN | POWERPC_EXCP_FP_VXCVI); + } else if (unlikely(float64_is_nan(FT0) || isinfinity(FT0))) { + /* qNan / infinity conversion */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXCVI); + } else { + p.i = float64_to_int64(FT0, &env->fp_status); + FT0 = p.d; + } } void do_fctidz (void) @@ -617,20 +1094,34 @@ void do_fctidz (void) uint64_t i; } p; - p.i = float64_to_int64_round_to_zero(FT0, &env->fp_status); - FT0 = p.d; + if (unlikely(float64_is_signaling_nan(FT0))) { + /* sNaN conversion */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN | POWERPC_EXCP_FP_VXCVI); + } else if (unlikely(float64_is_nan(FT0) || isinfinity(FT0))) { + /* qNan / infinity conversion */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXCVI); + } else { + p.i = float64_to_int64_round_to_zero(FT0, &env->fp_status); + FT0 = p.d; + } } #endif static always_inline void do_fri (int rounding_mode) { - int curmode; - - curmode = env->fp_status.float_rounding_mode; - set_float_rounding_mode(rounding_mode, &env->fp_status); - FT0 = float64_round_to_int(FT0, &env->fp_status); - set_float_rounding_mode(curmode, &env->fp_status); + if (unlikely(float64_is_signaling_nan(FT0))) { + /* sNaN round */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN | POWERPC_EXCP_FP_VXCVI); + } else if (unlikely(float64_is_nan(FT0) || isinfinity(FT0))) { + /* qNan / infinity round */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXCVI); + } else { + set_float_rounding_mode(rounding_mode, &env->fp_status); + FT0 = float64_round_to_int(FT0, &env->fp_status); + /* Restore rounding mode from FPSCR */ + fpscr_set_rounding_mode(); + } } void do_frin (void) @@ -656,90 +1147,142 @@ void do_frim (void) #if USE_PRECISE_EMULATION void do_fmadd (void) { + if (unlikely(float64_is_signaling_nan(FT0) || + float64_is_signaling_nan(FT1) || + float64_is_signaling_nan(FT2))) { + /* sNaN operation */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN); + } else { #ifdef FLOAT128 - float128 ft0_128, ft1_128; - - ft0_128 = float64_to_float128(FT0, &env->fp_status); - ft1_128 = float64_to_float128(FT1, &env->fp_status); - ft0_128 = float128_mul(ft0_128, ft1_128, &env->fp_status); - ft1_128 = float64_to_float128(FT2, &env->fp_status); - ft0_128 = float128_add(ft0_128, ft1_128, &env->fp_status); - FT0 = float128_to_float64(ft0_128, &env->fp_status); + /* This is the way the PowerPC specification defines it */ + float128 ft0_128, ft1_128; + + ft0_128 = float64_to_float128(FT0, &env->fp_status); + ft1_128 = float64_to_float128(FT1, &env->fp_status); + ft0_128 = float128_mul(ft0_128, ft1_128, &env->fp_status); + ft1_128 = float64_to_float128(FT2, &env->fp_status); + ft0_128 = float128_add(ft0_128, ft1_128, &env->fp_status); + FT0 = float128_to_float64(ft0_128, &env->fp_status); #else - /* This is OK on x86 hosts */ - FT0 = (FT0 * FT1) + FT2; + /* This is OK on x86 hosts */ + FT0 = (FT0 * FT1) + FT2; #endif + } } void do_fmsub (void) { + if (unlikely(float64_is_signaling_nan(FT0) || + float64_is_signaling_nan(FT1) || + float64_is_signaling_nan(FT2))) { + /* sNaN operation */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN); + } else { #ifdef FLOAT128 - float128 ft0_128, ft1_128; - - ft0_128 = float64_to_float128(FT0, &env->fp_status); - ft1_128 = float64_to_float128(FT1, &env->fp_status); - ft0_128 = float128_mul(ft0_128, ft1_128, &env->fp_status); - ft1_128 = float64_to_float128(FT2, &env->fp_status); - ft0_128 = float128_sub(ft0_128, ft1_128, &env->fp_status); - FT0 = float128_to_float64(ft0_128, &env->fp_status); + /* This is the way the PowerPC specification defines it */ + float128 ft0_128, ft1_128; + + ft0_128 = float64_to_float128(FT0, &env->fp_status); + ft1_128 = float64_to_float128(FT1, &env->fp_status); + ft0_128 = float128_mul(ft0_128, ft1_128, &env->fp_status); + ft1_128 = float64_to_float128(FT2, &env->fp_status); + ft0_128 = float128_sub(ft0_128, ft1_128, &env->fp_status); + FT0 = float128_to_float64(ft0_128, &env->fp_status); #else - /* This is OK on x86 hosts */ - FT0 = (FT0 * FT1) - FT2; + /* This is OK on x86 hosts */ + FT0 = (FT0 * FT1) - FT2; #endif + } } #endif /* USE_PRECISE_EMULATION */ void do_fnmadd (void) { + if (unlikely(float64_is_signaling_nan(FT0) || + float64_is_signaling_nan(FT1) || + float64_is_signaling_nan(FT2))) { + /* sNaN operation */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN); + } else { #if USE_PRECISE_EMULATION #ifdef FLOAT128 - float128 ft0_128, ft1_128; - - ft0_128 = float64_to_float128(FT0, &env->fp_status); - ft1_128 = float64_to_float128(FT1, &env->fp_status); - ft0_128 = float128_mul(ft0_128, ft1_128, &env->fp_status); - ft1_128 = float64_to_float128(FT2, &env->fp_status); - ft0_128 = float128_add(ft0_128, ft1_128, &env->fp_status); - FT0 = float128_to_float64(ft0_128, &env->fp_status); + /* This is the way the PowerPC specification defines it */ + float128 ft0_128, ft1_128; + + ft0_128 = float64_to_float128(FT0, &env->fp_status); + ft1_128 = float64_to_float128(FT1, &env->fp_status); + ft0_128 = float128_mul(ft0_128, ft1_128, &env->fp_status); + ft1_128 = float64_to_float128(FT2, &env->fp_status); + ft0_128 = float128_add(ft0_128, ft1_128, &env->fp_status); + FT0 = float128_to_float64(ft0_128, &env->fp_status); #else - /* This is OK on x86 hosts */ - FT0 = (FT0 * FT1) + FT2; + /* This is OK on x86 hosts */ + FT0 = (FT0 * FT1) + FT2; #endif #else - FT0 = float64_mul(FT0, FT1, &env->fp_status); - FT0 = float64_add(FT0, FT2, &env->fp_status); + FT0 = float64_mul(FT0, FT1, &env->fp_status); + FT0 = float64_add(FT0, FT2, &env->fp_status); #endif - if (likely(!isnan(FT0))) - FT0 = float64_chs(FT0); + if (likely(!isnan(FT0))) + FT0 = float64_chs(FT0); + } } void do_fnmsub (void) { + if (unlikely(float64_is_signaling_nan(FT0) || + float64_is_signaling_nan(FT1) || + float64_is_signaling_nan(FT2))) { + /* sNaN operation */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN); + } else { #if USE_PRECISE_EMULATION #ifdef FLOAT128 - float128 ft0_128, ft1_128; - - ft0_128 = float64_to_float128(FT0, &env->fp_status); - ft1_128 = float64_to_float128(FT1, &env->fp_status); - ft0_128 = float128_mul(ft0_128, ft1_128, &env->fp_status); - ft1_128 = float64_to_float128(FT2, &env->fp_status); - ft0_128 = float128_sub(ft0_128, ft1_128, &env->fp_status); - FT0 = float128_to_float64(ft0_128, &env->fp_status); + /* This is the way the PowerPC specification defines it */ + float128 ft0_128, ft1_128; + + ft0_128 = float64_to_float128(FT0, &env->fp_status); + ft1_128 = float64_to_float128(FT1, &env->fp_status); + ft0_128 = float128_mul(ft0_128, ft1_128, &env->fp_status); + ft1_128 = float64_to_float128(FT2, &env->fp_status); + ft0_128 = float128_sub(ft0_128, ft1_128, &env->fp_status); + FT0 = float128_to_float64(ft0_128, &env->fp_status); #else - /* This is OK on x86 hosts */ - FT0 = (FT0 * FT1) - FT2; + /* This is OK on x86 hosts */ + FT0 = (FT0 * FT1) - FT2; #endif #else - FT0 = float64_mul(FT0, FT1, &env->fp_status); - FT0 = float64_sub(FT0, FT2, &env->fp_status); + FT0 = float64_mul(FT0, FT1, &env->fp_status); + FT0 = float64_sub(FT0, FT2, &env->fp_status); #endif - if (likely(!isnan(FT0))) - FT0 = float64_chs(FT0); + if (likely(!isnan(FT0))) + FT0 = float64_chs(FT0); + } } +#if USE_PRECISE_EMULATION +void do_frsp (void) +{ + if (unlikely(float64_is_signaling_nan(FT0))) { + /* sNaN square root */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN); + } else { + FT0 = float64_to_float32(FT0, &env->fp_status); + } +} +#endif /* USE_PRECISE_EMULATION */ + void do_fsqrt (void) { - FT0 = float64_sqrt(FT0, &env->fp_status); + if (unlikely(float64_is_signaling_nan(FT0))) { + /* sNaN square root */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN); + } else if (unlikely(fpisneg(FT0) && !iszero(FT0))) { + /* Square root of a negative nonzero number */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSQRT); + } else { + FT0 = float64_sqrt(FT0, &env->fp_status); + } } void do_fre (void) @@ -749,7 +1292,13 @@ void do_fre (void) uint64_t i; } p; - if (likely(isnormal(FT0))) { + if (unlikely(float64_is_signaling_nan(FT0))) { + /* sNaN reciprocal */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN); + } else if (unlikely(iszero(FT0))) { + /* Zero reciprocal */ + float_zero_divide_excp(); + } else if (likely(isnormal(FT0))) { FT0 = float64_div(1.0, FT0, &env->fp_status); } else { p.d = FT0; @@ -759,7 +1308,7 @@ void do_fre (void) p.i = 0x7FF0000000000000ULL; } else if (isnan(FT0)) { p.i = 0x7FF8000000000000ULL; - } else if (FT0 < 0.0) { + } else if (fpisneg(FT0)) { p.i = 0x8000000000000000ULL; } else { p.i = 0x0000000000000000ULL; @@ -775,7 +1324,13 @@ void do_fres (void) uint64_t i; } p; - if (likely(isnormal(FT0))) { + if (unlikely(float64_is_signaling_nan(FT0))) { + /* sNaN reciprocal */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN); + } else if (unlikely(iszero(FT0))) { + /* Zero reciprocal */ + float_zero_divide_excp(); + } else if (likely(isnormal(FT0))) { #if USE_PRECISE_EMULATION FT0 = float64_div(1.0, FT0, &env->fp_status); FT0 = float64_to_float32(FT0, &env->fp_status); @@ -790,7 +1345,7 @@ void do_fres (void) p.i = 0x7FF0000000000000ULL; } else if (isnan(FT0)) { p.i = 0x7FF8000000000000ULL; - } else if (FT0 < 0.0) { + } else if (fpisneg(FT0)) { p.i = 0x8000000000000000ULL; } else { p.i = 0x0000000000000000ULL; @@ -806,7 +1361,13 @@ void do_frsqrte (void) uint64_t i; } p; - if (likely(isnormal(FT0) && FT0 > 0.0)) { + if (unlikely(float64_is_signaling_nan(FT0))) { + /* sNaN reciprocal square root */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN); + } else if (unlikely(fpisneg(FT0) && !iszero(FT0))) { + /* Reciprocal square root of a negative nonzero number */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSQRT); + } else if (likely(isnormal(FT0))) { FT0 = float64_sqrt(FT0, &env->fp_status); FT0 = float32_div(1.0, FT0, &env->fp_status); } else { @@ -816,9 +1377,8 @@ void do_frsqrte (void) } else if (p.i == 0x0000000000000000ULL) { p.i = 0x7FF0000000000000ULL; } else if (isnan(FT0)) { - if (!(p.i & 0x0008000000000000ULL)) - p.i |= 0x000FFFFFFFFFFFFFULL; - } else if (FT0 < 0) { + p.i |= 0x000FFFFFFFFFFFFFULL; + } else if (fpisneg(FT0)) { p.i = 0x7FF8000000000000ULL; } else { p.i = 0x0000000000000000ULL; @@ -829,7 +1389,7 @@ void do_frsqrte (void) void do_fsel (void) { - if (FT0 >= 0) + if (!fpisneg(FT0) || iszero(FT0)) FT0 = FT1; else FT0 = FT2; @@ -837,7 +1397,11 @@ void do_fsel (void) void do_fcmpu (void) { - if (likely(!isnan(FT0) && !isnan(FT1))) { + if (unlikely(float64_is_signaling_nan(FT0) || + float64_is_signaling_nan(FT1))) { + /* sNaN comparison */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN); + } else { if (float64_lt(FT0, FT1, &env->fp_status)) { T0 = 0x08UL; } else if (!float64_le(FT0, FT1, &env->fp_status)) { @@ -845,18 +1409,25 @@ void do_fcmpu (void) } else { T0 = 0x02UL; } - } else { - T0 = 0x01UL; - env->fpscr[4] |= 0x1; - env->fpscr[6] |= 0x1; } - env->fpscr[3] = T0; + env->fpscr &= ~(0x0F << FPSCR_FPRF); + env->fpscr |= T0 << FPSCR_FPRF; } void do_fcmpo (void) { - env->fpscr[4] &= ~0x1; - if (likely(!isnan(FT0) && !isnan(FT1))) { + if (unlikely(float64_is_nan(FT0) || + float64_is_nan(FT1))) { + if (float64_is_signaling_nan(FT0) || + float64_is_signaling_nan(FT1)) { + /* sNaN comparison */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXSNAN | + POWERPC_EXCP_FP_VXVC); + } else { + /* qNaN comparison */ + fload_invalid_op_excp(POWERPC_EXCP_FP_VXVC); + } + } else { if (float64_lt(FT0, FT1, &env->fp_status)) { T0 = 0x08UL; } else if (!float64_le(FT0, FT1, &env->fp_status)) { @@ -864,19 +1435,9 @@ void do_fcmpo (void) } else { T0 = 0x02UL; } - } else { - T0 = 0x01UL; - env->fpscr[4] |= 0x1; - if (!float64_is_signaling_nan(FT0) || !float64_is_signaling_nan(FT1)) { - /* Quiet NaN case */ - env->fpscr[6] |= 0x1; - if (!(env->fpscr[1] & 0x8)) - env->fpscr[4] |= 0x8; - } else { - env->fpscr[4] |= 0x8; - } } - env->fpscr[3] = T0; + env->fpscr &= ~(0x0F << FPSCR_FPRF); + env->fpscr |= T0 << FPSCR_FPRF; } #if !defined (CONFIG_USER_ONLY) |