diff options
Diffstat (limited to 'src/emu')
| -rw-r--r-- | src/emu/x64rund8.c | 8 | ||||
| -rw-r--r-- | src/emu/x64rund9.c | 47 | ||||
| -rw-r--r-- | src/emu/x64runda.c | 7 | ||||
| -rw-r--r-- | src/emu/x64rundb.c | 8 | ||||
| -rw-r--r-- | src/emu/x64rundc.c | 7 | ||||
| -rw-r--r-- | src/emu/x64runde.c | 8 | ||||
| -rw-r--r-- | src/emu/x87emu_setround.h | 29 |
7 files changed, 104 insertions, 10 deletions
diff --git a/src/emu/x64rund8.c b/src/emu/x64rund8.c index 000eab21..fd5ccf66 100644 --- a/src/emu/x64rund8.c +++ b/src/emu/x64rund8.c @@ -1,4 +1,5 @@ #define _GNU_SOURCE +#include <fenv.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -17,6 +18,7 @@ #include "x64primop.h" #include "x64trace.h" #include "x87emu_private.h" +#include "x87emu_setround.h" #include "box64context.h" #include "bridge.h" @@ -34,6 +36,7 @@ uintptr_t RunD8(x64emu_t *emu, rex_t rex, uintptr_t addr, uintptr_t offs) #ifdef TEST_INTERPRETER x64emu_t*emu = test->emu; #endif + int oldround = fpu_setround(emu); nextop = F8; if(MODREG) @@ -121,6 +124,7 @@ uintptr_t RunD8(x64emu_t *emu, rex_t rex, uintptr_t addr, uintptr_t offs) ST0.d = ST(nextop&7).d / ST0.d; break; default: + fesetround(oldround); return 0; } else switch((nextop>>3)&7) { @@ -190,7 +194,9 @@ uintptr_t RunD8(x64emu_t *emu, rex_t rex, uintptr_t addr, uintptr_t offs) ST0.d = *(float*)ED / ST0.d; break; default: + fesetround(oldround); return 0; } - return addr; + fesetround(oldround); + return addr; } \ No newline at end of file diff --git a/src/emu/x64rund9.c b/src/emu/x64rund9.c index 648eb0a7..c7b8d308 100644 --- a/src/emu/x64rund9.c +++ b/src/emu/x64rund9.c @@ -1,4 +1,5 @@ #define _GNU_SOURCE +#include <fenv.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -17,6 +18,7 @@ #include "x64primop.h" #include "x64trace.h" #include "x87emu_private.h" +#include "x87emu_setround.h" #include "box64context.h" #include "bridge.h" @@ -33,6 +35,7 @@ uintptr_t RunD9(x64emu_t *emu, rex_t rex, uintptr_t addr, uintptr_t offs) uint64_t ll; float f; reg64_t *oped; + int oldround; #ifdef TEST_INTERPRETER x64emu_t*emu = test->emu; #endif @@ -123,20 +126,30 @@ uintptr_t RunD9(x64emu_t *emu, rex_t rex, uintptr_t addr, uintptr_t offs) break; case 0xF0: /* F2XM1 */ - ST0.d = exp2(ST0.d) - 1.0; + if (ST0.d == 0) + break; + // Using the expm1 instead of exp2(ST0)-1 can avoid losing precision much, + // expecially when ST0 is close to zero (which loses the precise when -1). + // printf("%a, %a\n", LN2 * ST0.d, expm1(LN2 * ST0.d)); + ST0.d = expm1(LN2 * ST0.d); + // = 2^ST0 - 1 + error. (in math) break; case 0xF1: /* FYL2X */ ST(1).d *= log2(ST0.d); fpu_do_pop(emu); break; case 0xF2: /* FPTAN */ + oldround = fpu_setround(emu); ST0.d = tan(ST0.d); + fesetround(oldround); fpu_do_push(emu); ST0.d = 1.0; emu->sw.f.F87_C2 = 0; break; case 0xF3: /* FPATAN */ + oldround = fpu_setround(emu); ST1.d = atan2(ST1.d, ST0.d); + fesetround(oldround); fpu_do_pop(emu); break; case 0xF4: /* FXTRACT */ @@ -209,15 +222,22 @@ uintptr_t RunD9(x64emu_t *emu, rex_t rex, uintptr_t addr, uintptr_t offs) emu->top=(emu->top+1)&7; // this will probably break a few things break; case 0xF9: /* FYL2XP1 */ - ST(1).d *= log2(ST0.d + 1.0); + // Using the log1p instead of log2(ST0+1) can avoid losing precision much, + // expecially when ST0 is close to zero (which loses the precise when +1). + ST(1).d = (ST(1).d * log1p(ST0.d)) / M_LN2; + // = ST1 * log2(ST0 + 1) + error. (in math) fpu_do_pop(emu); break; case 0xFA: /* FSQRT */ + oldround = fpu_setround(emu); ST0.d = sqrt(ST0.d); + fesetround(oldround); break; case 0xFB: /* FSINCOS */ fpu_do_push(emu); + oldround = fpu_setround(emu); sincos(ST1.d, &ST1.d, &ST0.d); + fesetround(oldround); emu->sw.f.F87_C2 = 0; break; case 0xFC: /* FRNDINT */ @@ -225,15 +245,28 @@ uintptr_t RunD9(x64emu_t *emu, rex_t rex, uintptr_t addr, uintptr_t offs) break; case 0xFD: /* FSCALE */ // this could probably be done by just altering the exponant part of the float... - if(ST0.d!=0.0) - ST0.d *= exp2(trunc(ST1.d)); + if (ST1.d > INT32_MAX) + tmp32s = INT32_MAX; + else if (ST1.d < INT32_MIN) + tmp32s = INT32_MIN; + else + tmp32s = ST1.d; + if(ST0.d!=0.0) { + oldround = fpu_setround(emu); + ST0.d = ldexp(ST0.d, tmp32s); + fesetround(oldround); + } break; case 0xFE: /* FSIN */ + oldround = fpu_setround(emu); ST0.d = sin(ST0.d); + fesetround(oldround); emu->sw.f.F87_C2 = 0; break; case 0xFF: /* FCOS */ + oldround = fpu_setround(emu); ST0.d = cos(ST0.d); + fesetround(oldround); emu->sw.f.F87_C2 = 0; break; @@ -257,7 +290,9 @@ uintptr_t RunD9(x64emu_t *emu, rex_t rex, uintptr_t addr, uintptr_t offs) } else { GETE4(0); } + oldround = fpu_setround(emu); *(float*)ED = ST0.d; + fesetround(oldround); break; case 3: /* FSTP Ed, ST0 */ if(offs) { @@ -265,7 +300,9 @@ uintptr_t RunD9(x64emu_t *emu, rex_t rex, uintptr_t addr, uintptr_t offs) } else { GETE4(0); } + oldround = fpu_setround(emu); *(float*)ED = ST0.d; + fesetround(oldround); fpu_do_pop(emu); break; case 4: /* FLDENV m */ @@ -309,5 +346,5 @@ uintptr_t RunD9(x64emu_t *emu, rex_t rex, uintptr_t addr, uintptr_t offs) default: return 0; } - return addr; + return addr; } \ No newline at end of file diff --git a/src/emu/x64runda.c b/src/emu/x64runda.c index aed775f9..056937e7 100644 --- a/src/emu/x64runda.c +++ b/src/emu/x64runda.c @@ -1,4 +1,5 @@ #define _GNU_SOURCE +#include <fenv.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -17,6 +18,7 @@ #include "x64primop.h" #include "x64trace.h" #include "x87emu_private.h" +#include "x87emu_setround.h" #include "box64context.h" #include "bridge.h" @@ -94,7 +96,8 @@ uintptr_t RunDA(x64emu_t *emu, rex_t rex, uintptr_t addr) default: return 0; - } else + } else { + int oldround = fpu_setround(emu); switch((nextop>>3)&7) { case 0: /* FIADD ST0, Ed int */ GETE4(0); @@ -130,5 +133,7 @@ uintptr_t RunDA(x64emu_t *emu, rex_t rex, uintptr_t addr) ST0.d = (double)ED->sdword[0] / ST0.d; break; } + fesetround(oldround); + } return addr; } \ No newline at end of file diff --git a/src/emu/x64rundb.c b/src/emu/x64rundb.c index 82ea43ff..b6ba0ff1 100644 --- a/src/emu/x64rundb.c +++ b/src/emu/x64rundb.c @@ -1,4 +1,5 @@ #define _GNU_SOURCE +#include <fenv.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -17,6 +18,7 @@ #include "x64primop.h" #include "x64trace.h" #include "x87emu_private.h" +#include "x87emu_setround.h" #include "box64context.h" #include "bridge.h" @@ -35,6 +37,7 @@ uintptr_t RunDB(x64emu_t *emu, rex_t rex, uintptr_t addr) x64emu_t*emu = test->emu; #endif + int oldround = fpu_setround(emu); nextop = F8; if(MODREG) switch(nextop) { @@ -128,6 +131,7 @@ uintptr_t RunDB(x64emu_t *emu, rex_t rex, uintptr_t addr) break; default: + fesetround(oldround); return 0; } else switch((nextop>>3)&7) { @@ -179,7 +183,9 @@ uintptr_t RunDB(x64emu_t *emu, rex_t rex, uintptr_t addr) fpu_do_pop(emu); break; default: + fesetround(oldround); return 0; } - return addr; + fesetround(oldround); + return addr; } diff --git a/src/emu/x64rundc.c b/src/emu/x64rundc.c index 6d9ff07b..c9ffe738 100644 --- a/src/emu/x64rundc.c +++ b/src/emu/x64rundc.c @@ -17,6 +17,7 @@ #include "x64primop.h" #include "x64trace.h" #include "x87emu_private.h" +#include "x87emu_setround.h" #include "box64context.h" #include "bridge.h" @@ -34,6 +35,7 @@ uintptr_t RunDC(x64emu_t *emu, rex_t rex, uintptr_t addr) x64emu_t*emu = test->emu; #endif + int oldround = fpu_setround(emu); nextop = F8; if(MODREG) switch(nextop) { @@ -119,6 +121,7 @@ uintptr_t RunDC(x64emu_t *emu, rex_t rex, uintptr_t addr) ST(nextop&7).d /= ST0.d; break; default: + fesetround(oldround); return 0; } else { GETE8(0); @@ -149,8 +152,10 @@ uintptr_t RunDC(x64emu_t *emu, rex_t rex, uintptr_t addr) ST0.d = *(double*)ED / ST0.d; break; default: + fesetround(oldround); return 0; } } - return addr; + fesetround(oldround); + return addr; } diff --git a/src/emu/x64runde.c b/src/emu/x64runde.c index 4cb5e4a2..8b082aa5 100644 --- a/src/emu/x64runde.c +++ b/src/emu/x64runde.c @@ -1,4 +1,5 @@ #define _GNU_SOURCE +#include <fenv.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -17,6 +18,7 @@ #include "x64primop.h" #include "x64trace.h" #include "x87emu_private.h" +#include "x87emu_setround.h" #include "box64context.h" #include "bridge.h" @@ -34,6 +36,7 @@ uintptr_t RunDE(x64emu_t *emu, rex_t rex, uintptr_t addr) x64emu_t*emu = test->emu; #endif + int oldround = fpu_setround(emu); nextop = F8; if(MODREG) switch(nextop) { @@ -126,6 +129,7 @@ uintptr_t RunDE(x64emu_t *emu, rex_t rex, uintptr_t addr) break; default: + fesetround(oldround); return 0; } else switch((nextop>>3)&7) { @@ -154,7 +158,9 @@ uintptr_t RunDE(x64emu_t *emu, rex_t rex, uintptr_t addr) ST0.d = (double)EW->sword[0] / ST0.d; break; default: + fesetround(oldround); return 0; } - return addr; + fesetround(oldround); + return addr; } \ No newline at end of file diff --git a/src/emu/x87emu_setround.h b/src/emu/x87emu_setround.h new file mode 100644 index 00000000..4791c3ea --- /dev/null +++ b/src/emu/x87emu_setround.h @@ -0,0 +1,29 @@ +#ifndef __SETROUND_H__ +#define __SETROUND_H__ +#pragma STDC FENV_ACCESS ON +#include <fenv.h> +#include <stdint.h> +#include "x64emu.h" +#include "x64emu_private.h" +// set the rounding mode to the emulator's one, and return the old one +static inline int fpu_setround(x64emu_t* emu) { + int ret = fegetround(); + int rounding_direction; + switch (emu->cw.f.C87_RD) { + case ROUND_Nearest: + rounding_direction = FE_TONEAREST; + break; + case ROUND_Down: + rounding_direction = FE_DOWNWARD; + break; + case ROUND_Up: + rounding_direction = FE_UPWARD; + break; + case ROUND_Chop: + rounding_direction = FE_TOWARDZERO; + break; + } + fesetround(rounding_direction); + return ret; +} +#endif \ No newline at end of file |