mirror of
https://github.com/yuzu-emu/unicorn.git
synced 2024-12-23 05:25:31 +00:00
target/i386: fix IEEE x87 floating-point exception raising
Most x87 instruction implementations fail to raise the expected IEEE floating-point exceptions because they do nothing to convert the exception state from the softfloat machinery into the exception flags in the x87 status word. There is special-case handling of division to raise the divide-by-zero exception, but that handling is itself buggy: it raises the exception in inappropriate cases (inf / 0 and nan / 0, which should not raise any exceptions, and 0 / 0, which should raise "invalid" instead). Fix this by converting the floating-point exceptions raised during an operation by the softfloat machinery into exceptions in the x87 status word (passing through the existing fpu_set_exception function for handling related to trapping exceptions). There are special cases where some functions convert to integer internally but exceptions from that conversion are not always correct exceptions for the instruction to raise. There might be scope for some simplification if the softfloat exception state either could always be assumed to be in sync with the state in the status word, or could always be ignored at the start of each instruction and just set to 0 then; I haven't looked into that in detail, and it might run into interactions with the various ways the emulation does not yet handle trapping exceptions properly. I think the approach taken here, of saving the softfloat state, setting exceptions there to 0 and then merging the old exceptions back in after carrying out the operation, is conservatively safe Backports commit 975af797f1e04e4d1b1a12f1731141d3770fdbce from qemu
This commit is contained in:
parent
cb50df6aae
commit
e79024e0cf
|
@ -127,12 +127,32 @@ static void fpu_set_exception(CPUX86State *env, int mask)
|
|||
}
|
||||
}
|
||||
|
||||
static inline uint8_t save_exception_flags(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = get_float_exception_flags(&env->fp_status);
|
||||
set_float_exception_flags(0, &env->fp_status);
|
||||
return old_flags;
|
||||
}
|
||||
|
||||
static void merge_exception_flags(CPUX86State *env, uint8_t old_flags)
|
||||
{
|
||||
uint8_t new_flags = get_float_exception_flags(&env->fp_status);
|
||||
float_raise(old_flags, &env->fp_status);
|
||||
fpu_set_exception(env,
|
||||
((new_flags & float_flag_invalid ? FPUS_IE : 0) |
|
||||
(new_flags & float_flag_divbyzero ? FPUS_ZE : 0) |
|
||||
(new_flags & float_flag_overflow ? FPUS_OE : 0) |
|
||||
(new_flags & float_flag_underflow ? FPUS_UE : 0) |
|
||||
(new_flags & float_flag_inexact ? FPUS_PE : 0) |
|
||||
(new_flags & float_flag_input_denormal ? FPUS_DE : 0)));
|
||||
}
|
||||
|
||||
static inline floatx80 helper_fdiv(CPUX86State *env, floatx80 a, floatx80 b)
|
||||
{
|
||||
if (floatx80_is_zero(b)) {
|
||||
fpu_set_exception(env, FPUS_ZE);
|
||||
}
|
||||
return floatx80_div(a, b, &env->fp_status);
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
floatx80 ret = floatx80_div(a, b, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void fpu_raise_exception(CPUX86State *env, uintptr_t retaddr)
|
||||
|
@ -149,6 +169,7 @@ static void fpu_raise_exception(CPUX86State *env, uintptr_t retaddr)
|
|||
|
||||
void helper_flds_FT0(CPUX86State *env, uint32_t val)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
union {
|
||||
float32 f;
|
||||
uint32_t i;
|
||||
|
@ -156,10 +177,12 @@ void helper_flds_FT0(CPUX86State *env, uint32_t val)
|
|||
|
||||
u.i = val;
|
||||
FT0 = float32_to_floatx80(u.f, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fldl_FT0(CPUX86State *env, uint64_t val)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
union {
|
||||
float64 f;
|
||||
uint64_t i;
|
||||
|
@ -167,6 +190,7 @@ void helper_fldl_FT0(CPUX86State *env, uint64_t val)
|
|||
|
||||
u.i = val;
|
||||
FT0 = float64_to_floatx80(u.f, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fildl_FT0(CPUX86State *env, int32_t val)
|
||||
|
@ -176,6 +200,7 @@ void helper_fildl_FT0(CPUX86State *env, int32_t val)
|
|||
|
||||
void helper_flds_ST0(CPUX86State *env, uint32_t val)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
int new_fpstt;
|
||||
union {
|
||||
float32 f;
|
||||
|
@ -187,10 +212,12 @@ void helper_flds_ST0(CPUX86State *env, uint32_t val)
|
|||
env->fpregs[new_fpstt].d = float32_to_floatx80(u.f, &env->fp_status);
|
||||
env->fpstt = new_fpstt;
|
||||
env->fptags[new_fpstt] = 0; /* validate stack entry */
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fldl_ST0(CPUX86State *env, uint64_t val)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
int new_fpstt;
|
||||
union {
|
||||
float64 f;
|
||||
|
@ -202,6 +229,7 @@ void helper_fldl_ST0(CPUX86State *env, uint64_t val)
|
|||
env->fpregs[new_fpstt].d = float64_to_floatx80(u.f, &env->fp_status);
|
||||
env->fpstt = new_fpstt;
|
||||
env->fptags[new_fpstt] = 0; /* validate stack entry */
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fildl_ST0(CPUX86State *env, int32_t val)
|
||||
|
@ -226,113 +254,108 @@ void helper_fildll_ST0(CPUX86State *env, int64_t val)
|
|||
|
||||
uint32_t helper_fsts_ST0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
union {
|
||||
float32 f;
|
||||
uint32_t i;
|
||||
} u;
|
||||
|
||||
u.f = floatx80_to_float32(ST0, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
return u.i;
|
||||
}
|
||||
|
||||
uint64_t helper_fstl_ST0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
union {
|
||||
float64 f;
|
||||
uint64_t i;
|
||||
} u;
|
||||
|
||||
u.f = floatx80_to_float64(ST0, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
return u.i;
|
||||
}
|
||||
|
||||
int32_t helper_fist_ST0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
int32_t val;
|
||||
|
||||
val = floatx80_to_int32(ST0, &env->fp_status);
|
||||
if (val != (int16_t)val) {
|
||||
set_float_exception_flags(float_flag_invalid, &env->fp_status);
|
||||
val = -32768;
|
||||
}
|
||||
merge_exception_flags(env, old_flags);
|
||||
return val;
|
||||
}
|
||||
|
||||
int32_t helper_fistl_ST0(CPUX86State *env)
|
||||
{
|
||||
int32_t val;
|
||||
signed char old_exp_flags;
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
|
||||
old_exp_flags = get_float_exception_flags(&env->fp_status);
|
||||
set_float_exception_flags(0, &env->fp_status);
|
||||
|
||||
val = floatx80_to_int32(ST0, &env->fp_status);
|
||||
if (get_float_exception_flags(&env->fp_status) & float_flag_invalid) {
|
||||
val = 0x80000000;
|
||||
}
|
||||
set_float_exception_flags(get_float_exception_flags(&env->fp_status)
|
||||
| old_exp_flags, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
return val;
|
||||
}
|
||||
|
||||
int64_t helper_fistll_ST0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
int64_t val;
|
||||
signed char old_exp_flags;
|
||||
|
||||
old_exp_flags = get_float_exception_flags(&env->fp_status);
|
||||
set_float_exception_flags(0, &env->fp_status);
|
||||
|
||||
val = floatx80_to_int64(ST0, &env->fp_status);
|
||||
if (get_float_exception_flags(&env->fp_status) & float_flag_invalid) {
|
||||
val = 0x8000000000000000ULL;
|
||||
}
|
||||
set_float_exception_flags(get_float_exception_flags(&env->fp_status)
|
||||
| old_exp_flags, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
return val;
|
||||
}
|
||||
|
||||
int32_t helper_fistt_ST0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
int32_t val;
|
||||
|
||||
val = floatx80_to_int32_round_to_zero(ST0, &env->fp_status);
|
||||
if (val != (int16_t)val) {
|
||||
set_float_exception_flags(float_flag_invalid, &env->fp_status);
|
||||
val = -32768;
|
||||
}
|
||||
merge_exception_flags(env, old_flags);
|
||||
return val;
|
||||
}
|
||||
|
||||
int32_t helper_fisttl_ST0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
int32_t val;
|
||||
signed char old_exp_flags;
|
||||
|
||||
old_exp_flags = get_float_exception_flags(&env->fp_status);
|
||||
set_float_exception_flags(0, &env->fp_status);
|
||||
|
||||
val = floatx80_to_int32_round_to_zero(ST0, &env->fp_status);
|
||||
if (get_float_exception_flags(&env->fp_status) & float_flag_invalid) {
|
||||
val = 0x80000000;
|
||||
}
|
||||
set_float_exception_flags(get_float_exception_flags(&env->fp_status)
|
||||
| old_exp_flags, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
return val;
|
||||
}
|
||||
|
||||
int64_t helper_fisttll_ST0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
int64_t val;
|
||||
signed char old_exp_flags;
|
||||
|
||||
old_exp_flags = get_float_exception_flags(&env->fp_status);
|
||||
set_float_exception_flags(0, &env->fp_status);
|
||||
|
||||
val = floatx80_to_int64_round_to_zero(ST0, &env->fp_status);
|
||||
if (get_float_exception_flags(&env->fp_status) & float_flag_invalid) {
|
||||
val = 0x8000000000000000ULL;
|
||||
}
|
||||
set_float_exception_flags(get_float_exception_flags(&env->fp_status)
|
||||
| old_exp_flags, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
return val;
|
||||
}
|
||||
|
||||
|
@ -415,24 +438,29 @@ static const int fcom_ccval[4] = {0x0100, 0x4000, 0x0000, 0x4500};
|
|||
|
||||
void helper_fcom_ST0_FT0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
FloatRelation ret;
|
||||
|
||||
ret = floatx80_compare(ST0, FT0, &env->fp_status);
|
||||
env->fpus = (env->fpus & ~0x4500) | fcom_ccval[ret + 1];
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fucom_ST0_FT0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
FloatRelation ret;
|
||||
|
||||
ret = floatx80_compare_quiet(ST0, FT0, &env->fp_status);
|
||||
env->fpus = (env->fpus & ~0x4500) | fcom_ccval[ret + 1];
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
static const int fcomi_ccval[4] = {CC_C, CC_Z, 0, CC_Z | CC_P | CC_C};
|
||||
|
||||
void helper_fcomi_ST0_FT0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
int eflags;
|
||||
FloatRelation ret;
|
||||
|
||||
|
@ -440,10 +468,12 @@ void helper_fcomi_ST0_FT0(CPUX86State *env)
|
|||
eflags = cpu_cc_compute_all(env, CC_OP);
|
||||
eflags = (eflags & ~(CC_Z | CC_P | CC_C)) | fcomi_ccval[ret + 1];
|
||||
CC_SRC = eflags;
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fucomi_ST0_FT0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
int eflags;
|
||||
FloatRelation ret;
|
||||
|
||||
|
@ -451,26 +481,35 @@ void helper_fucomi_ST0_FT0(CPUX86State *env)
|
|||
eflags = cpu_cc_compute_all(env, CC_OP);
|
||||
eflags = (eflags & ~(CC_Z | CC_P | CC_C)) | fcomi_ccval[ret + 1];
|
||||
CC_SRC = eflags;
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fadd_ST0_FT0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
ST0 = floatx80_add(ST0, FT0, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fmul_ST0_FT0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
ST0 = floatx80_mul(ST0, FT0, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fsub_ST0_FT0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
ST0 = floatx80_sub(ST0, FT0, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fsubr_ST0_FT0(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
ST0 = floatx80_sub(FT0, ST0, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fdiv_ST0_FT0(CPUX86State *env)
|
||||
|
@ -487,22 +526,30 @@ void helper_fdivr_ST0_FT0(CPUX86State *env)
|
|||
|
||||
void helper_fadd_STN_ST0(CPUX86State *env, int st_index)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
ST(st_index) = floatx80_add(ST(st_index), ST0, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fmul_STN_ST0(CPUX86State *env, int st_index)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
ST(st_index) = floatx80_mul(ST(st_index), ST0, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fsub_STN_ST0(CPUX86State *env, int st_index)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
ST(st_index) = floatx80_sub(ST(st_index), ST0, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fsubr_STN_ST0(CPUX86State *env, int st_index)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
ST(st_index) = floatx80_sub(ST0, ST(st_index), &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fdiv_STN_ST0(CPUX86State *env, int st_index)
|
||||
|
@ -713,6 +760,7 @@ void helper_fbld_ST0(CPUX86State *env, target_ulong ptr)
|
|||
|
||||
void helper_fbst_ST0(CPUX86State *env, target_ulong ptr)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
int v;
|
||||
target_ulong mem_ref, mem_end;
|
||||
int64_t val;
|
||||
|
@ -723,13 +771,14 @@ void helper_fbst_ST0(CPUX86State *env, target_ulong ptr)
|
|||
val = floatx80_to_int64(ST0, &env->fp_status);
|
||||
mem_ref = ptr;
|
||||
if (val >= 1000000000000000000LL || val <= -1000000000000000000LL) {
|
||||
float_raise(float_flag_invalid, &env->fp_status);
|
||||
set_float_exception_flags(float_flag_invalid, &env->fp_status);
|
||||
while (mem_ref < ptr + 7) {
|
||||
cpu_stb_data_ra(env, mem_ref++, 0, GETPC());
|
||||
}
|
||||
cpu_stb_data_ra(env, mem_ref++, 0xc0, GETPC());
|
||||
cpu_stb_data_ra(env, mem_ref++, 0xff, GETPC());
|
||||
cpu_stb_data_ra(env, mem_ref++, 0xff, GETPC());
|
||||
merge_exception_flags(env, old_flags);
|
||||
return;
|
||||
}
|
||||
mem_end = mem_ref + 9;
|
||||
|
@ -751,6 +800,7 @@ void helper_fbst_ST0(CPUX86State *env, target_ulong ptr)
|
|||
while (mem_ref < mem_end) {
|
||||
cpu_stb_data_ra(env, mem_ref++, 0, GETPC());
|
||||
}
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_f2xm1(CPUX86State *env)
|
||||
|
@ -804,6 +854,7 @@ void helper_fpatan(CPUX86State *env)
|
|||
|
||||
void helper_fxtract(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
CPU_LDoubleU temp;
|
||||
|
||||
temp.d = ST0;
|
||||
|
@ -847,6 +898,7 @@ void helper_fxtract(CPUX86State *env)
|
|||
BIASEXPONENT(temp);
|
||||
ST0 = temp.d;
|
||||
}
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fprem1(CPUX86State *env)
|
||||
|
@ -986,11 +1038,13 @@ void helper_fyl2xp1(CPUX86State *env)
|
|||
|
||||
void helper_fsqrt(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
if (floatx80_is_neg(ST0)) {
|
||||
env->fpus &= ~0x4700; /* (C3,C2,C1,C0) <-- 0000 */
|
||||
env->fpus |= 0x400;
|
||||
}
|
||||
ST0 = floatx80_sqrt(ST0, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fsincos(CPUX86State *env)
|
||||
|
@ -1010,15 +1064,21 @@ void helper_fsincos(CPUX86State *env)
|
|||
|
||||
void helper_frndint(CPUX86State *env)
|
||||
{
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
ST0 = floatx80_round_to_int(ST0, &env->fp_status);
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fscale(CPUX86State *env)
|
||||
{
|
||||
if (floatx80_invalid_encoding(ST1)) {
|
||||
uint8_t old_flags = save_exception_flags(env);
|
||||
if (floatx80_invalid_encoding(ST1) || floatx80_invalid_encoding(ST0)) {
|
||||
float_raise(float_flag_invalid, &env->fp_status);
|
||||
ST0 = floatx80_default_nan(&env->fp_status);
|
||||
} else if (floatx80_is_any_nan(ST1)) {
|
||||
if (floatx80_is_signaling_nan(ST0, &env->fp_status)) {
|
||||
float_raise(float_flag_invalid, &env->fp_status);
|
||||
}
|
||||
ST0 = ST1;
|
||||
if (floatx80_is_signaling_nan(ST0, &env->fp_status)) {
|
||||
float_raise(float_flag_invalid, &env->fp_status);
|
||||
|
@ -1047,12 +1107,17 @@ void helper_fscale(CPUX86State *env)
|
|||
}
|
||||
}
|
||||
} else {
|
||||
int n = floatx80_to_int32_round_to_zero(ST1, &env->fp_status);
|
||||
int n;
|
||||
signed char save = env->fp_status.floatx80_rounding_precision;
|
||||
uint8_t save_flags = get_float_exception_flags(&env->fp_status);
|
||||
set_float_exception_flags(0, &env->fp_status);
|
||||
n = floatx80_to_int32_round_to_zero(ST1, &env->fp_status);
|
||||
set_float_exception_flags(save_flags, &env->fp_status);
|
||||
env->fp_status.floatx80_rounding_precision = 80;
|
||||
ST0 = floatx80_scalbn(ST0, n, &env->fp_status);
|
||||
env->fp_status.floatx80_rounding_precision = save;
|
||||
}
|
||||
merge_exception_flags(env, old_flags);
|
||||
}
|
||||
|
||||
void helper_fsin(CPUX86State *env)
|
||||
|
|
Loading…
Reference in a new issue