544 lines
16 KiB
C
544 lines
16 KiB
C
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <wchar.h>
|
|
#define fplog 0
|
|
#include <math.h>
|
|
#define HAVE_STDARG_H
|
|
#include <86box/86box.h>
|
|
#include "cpu.h"
|
|
#include <86box/mem.h>
|
|
#include <86box/pic.h>
|
|
#include "x86.h"
|
|
#include "x86_flags.h"
|
|
#include "x86_ops.h"
|
|
#include "x86seg_common.h"
|
|
#include "x87.h"
|
|
#include "386_common.h"
|
|
#include "softfloat/softfloat-specialize.h"
|
|
|
|
uint32_t x87_pc_off;
|
|
uint32_t x87_op_off;
|
|
uint16_t x87_pc_seg;
|
|
uint16_t x87_op_seg;
|
|
|
|
#ifdef ENABLE_FPU_LOG
|
|
int fpu_do_log = ENABLE_FPU_LOG;
|
|
|
|
void
|
|
fpu_log(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
if (fpu_do_log) {
|
|
va_start(ap, fmt);
|
|
pclog_ex(fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
}
|
|
#else
|
|
# define fpu_log(fmt, ...)
|
|
#endif
|
|
|
|
#ifdef USE_NEW_DYNAREC
|
|
uint16_t
|
|
x87_gettag(void)
|
|
{
|
|
uint16_t ret = 0;
|
|
|
|
for (uint8_t c = 0; c < 8; c++) {
|
|
if (cpu_state.tag[c] == TAG_EMPTY)
|
|
ret |= X87_TAG_EMPTY << (c * 2);
|
|
else if (cpu_state.tag[c] & TAG_UINT64)
|
|
ret |= 2 << (c * 2);
|
|
else if (cpu_state.ST[c] == 0.0 && !cpu_state.ismmx)
|
|
ret |= X87_TAG_ZERO << (c * 2);
|
|
else
|
|
ret |= X87_TAG_VALID << (c * 2);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
x87_settag(uint16_t new_tag)
|
|
{
|
|
for (uint8_t c = 0; c < 8; c++) {
|
|
int tag = (new_tag >> (c * 2)) & 3;
|
|
|
|
if (tag == X87_TAG_EMPTY)
|
|
cpu_state.tag[c] = TAG_EMPTY;
|
|
else if (tag == 2)
|
|
cpu_state.tag[c] = TAG_VALID | TAG_UINT64;
|
|
else
|
|
cpu_state.tag[c] = TAG_VALID;
|
|
}
|
|
}
|
|
#else
|
|
uint16_t
|
|
x87_gettag(void)
|
|
{
|
|
uint16_t ret = 0;
|
|
int c;
|
|
|
|
for (c = 0; c < 8; c++) {
|
|
if (cpu_state.tag[c] & TAG_UINT64)
|
|
ret |= 2 << (c * 2);
|
|
else
|
|
ret |= (cpu_state.tag[c] << (c * 2));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
x87_settag(uint16_t new_tag)
|
|
{
|
|
cpu_state.tag[0] = new_tag & 3;
|
|
cpu_state.tag[1] = (new_tag >> 2) & 3;
|
|
cpu_state.tag[2] = (new_tag >> 4) & 3;
|
|
cpu_state.tag[3] = (new_tag >> 6) & 3;
|
|
cpu_state.tag[4] = (new_tag >> 8) & 3;
|
|
cpu_state.tag[5] = (new_tag >> 10) & 3;
|
|
cpu_state.tag[6] = (new_tag >> 12) & 3;
|
|
cpu_state.tag[7] = (new_tag >> 14) & 3;
|
|
}
|
|
#endif
|
|
|
|
static floatx80
|
|
FPU_handle_NaN32_Func(floatx80 a, int aIsNaN, float32 b32, int bIsNaN, struct float_status_t *status)
|
|
{
|
|
int aIsSignalingNaN = floatx80_is_signaling_nan(a);
|
|
int bIsSignalingNaN = float32_is_signaling_nan(b32);
|
|
|
|
if (aIsSignalingNaN | bIsSignalingNaN)
|
|
float_raise(status, float_flag_invalid);
|
|
|
|
// propagate QNaN to SNaN
|
|
a = propagateFloatx80NaNOne(a, status);
|
|
|
|
if (aIsNaN & !bIsNaN)
|
|
return a;
|
|
|
|
// float32 is NaN so conversion will propagate SNaN to QNaN and raise
|
|
// appropriate exception flags
|
|
floatx80 b = float32_to_floatx80(b32, status);
|
|
|
|
if (aIsSignalingNaN) {
|
|
if (bIsSignalingNaN)
|
|
goto returnLargerSignificand;
|
|
return bIsNaN ? b : a;
|
|
} else if (aIsNaN) {
|
|
if (bIsSignalingNaN)
|
|
return a;
|
|
returnLargerSignificand:
|
|
if (a.fraction < b.fraction)
|
|
return b;
|
|
if (b.fraction < a.fraction)
|
|
return a;
|
|
return (a.exp < b.exp) ? a : b;
|
|
} else {
|
|
return b;
|
|
}
|
|
}
|
|
|
|
int
|
|
FPU_handle_NaN32(floatx80 a, float32 b, floatx80 *r, struct float_status_t *status)
|
|
{
|
|
const floatx80 floatx80_default_nan = packFloatx80(0, floatx80_default_nan_exp, floatx80_default_nan_fraction);
|
|
|
|
if (floatx80_is_unsupported(a)) {
|
|
float_raise(status, float_flag_invalid);
|
|
*r = floatx80_default_nan;
|
|
return 1;
|
|
}
|
|
|
|
int aIsNaN = floatx80_is_nan(a);
|
|
int bIsNaN = float32_is_nan(b);
|
|
if (aIsNaN | bIsNaN) {
|
|
*r = FPU_handle_NaN32_Func(a, aIsNaN, b, bIsNaN, status);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static floatx80
|
|
FPU_handle_NaN64_Func(floatx80 a, int aIsNaN, float64 b64, int bIsNaN, struct float_status_t *status)
|
|
{
|
|
int aIsSignalingNaN = floatx80_is_signaling_nan(a);
|
|
int bIsSignalingNaN = float64_is_signaling_nan(b64);
|
|
|
|
if (aIsSignalingNaN | bIsSignalingNaN)
|
|
float_raise(status, float_flag_invalid);
|
|
|
|
// propagate QNaN to SNaN
|
|
a = propagateFloatx80NaNOne(a, status);
|
|
|
|
if (aIsNaN & !bIsNaN)
|
|
return a;
|
|
|
|
// float64 is NaN so conversion will propagate SNaN to QNaN and raise
|
|
// appropriate exception flags
|
|
floatx80 b = float64_to_floatx80(b64, status);
|
|
|
|
if (aIsSignalingNaN) {
|
|
if (bIsSignalingNaN)
|
|
goto returnLargerSignificand;
|
|
return bIsNaN ? b : a;
|
|
} else if (aIsNaN) {
|
|
if (bIsSignalingNaN)
|
|
return a;
|
|
returnLargerSignificand:
|
|
if (a.fraction < b.fraction)
|
|
return b;
|
|
if (b.fraction < a.fraction)
|
|
return a;
|
|
return (a.exp < b.exp) ? a : b;
|
|
} else {
|
|
return b;
|
|
}
|
|
}
|
|
|
|
int
|
|
FPU_handle_NaN64(floatx80 a, float64 b, floatx80 *r, struct float_status_t *status)
|
|
{
|
|
const floatx80 floatx80_default_nan = packFloatx80(0, floatx80_default_nan_exp, floatx80_default_nan_fraction);
|
|
|
|
if (floatx80_is_unsupported(a)) {
|
|
float_raise(status, float_flag_invalid);
|
|
*r = floatx80_default_nan;
|
|
return 1;
|
|
}
|
|
|
|
int aIsNaN = floatx80_is_nan(a);
|
|
int bIsNaN = float64_is_nan(b);
|
|
if (aIsNaN | bIsNaN) {
|
|
*r = FPU_handle_NaN64_Func(a, aIsNaN, b, bIsNaN, status);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct float_status_t
|
|
i387cw_to_softfloat_status_word(uint16_t control_word)
|
|
{
|
|
struct float_status_t status;
|
|
int precision = control_word & FPU_CW_PC;
|
|
|
|
switch (precision) {
|
|
case FPU_PR_32_BITS:
|
|
status.float_rounding_precision = 32;
|
|
break;
|
|
case FPU_PR_64_BITS:
|
|
status.float_rounding_precision = 64;
|
|
break;
|
|
case FPU_PR_80_BITS:
|
|
status.float_rounding_precision = 80;
|
|
break;
|
|
default:
|
|
/* With the precision control bits set to 01 "(reserved)", a
|
|
real CPU behaves as if the precision control bits were
|
|
set to 11 "80 bits" */
|
|
status.float_rounding_precision = 80;
|
|
break;
|
|
}
|
|
|
|
status.float_exception_flags = 0; // clear exceptions before execution
|
|
status.float_nan_handling_mode = float_first_operand_nan;
|
|
status.float_rounding_mode = (control_word & FPU_CW_RC) >> 10;
|
|
status.flush_underflow_to_zero = 0;
|
|
status.float_suppress_exception = 0;
|
|
status.float_exception_masks = control_word & FPU_CW_Exceptions_Mask;
|
|
status.denormals_are_zeros = 0;
|
|
return status;
|
|
}
|
|
|
|
int
|
|
FPU_status_word_flags_fpu_compare(int float_relation)
|
|
{
|
|
switch (float_relation) {
|
|
case float_relation_unordered:
|
|
return (C0 | C2 | C3);
|
|
|
|
case float_relation_greater:
|
|
return 0;
|
|
|
|
case float_relation_less:
|
|
return C0;
|
|
|
|
case float_relation_equal:
|
|
return C3;
|
|
}
|
|
|
|
return (-1); // should never get here
|
|
}
|
|
|
|
void
|
|
FPU_write_eflags_fpu_compare(int float_relation)
|
|
{
|
|
switch (float_relation) {
|
|
case float_relation_unordered:
|
|
cpu_state.flags |= (Z_FLAG | P_FLAG | C_FLAG);
|
|
break;
|
|
|
|
case float_relation_greater:
|
|
break;
|
|
|
|
case float_relation_less:
|
|
cpu_state.flags |= C_FLAG;
|
|
break;
|
|
|
|
case float_relation_equal:
|
|
cpu_state.flags |= Z_FLAG;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint16_t
|
|
FPU_exception(uint32_t fetchdat, uint16_t exceptions, int store)
|
|
{
|
|
uint16_t status;
|
|
uint16_t unmasked;
|
|
|
|
/* Extract only the bits which we use to set the status word */
|
|
exceptions &= FPU_SW_Exceptions_Mask;
|
|
status = fpu_state.swd;
|
|
|
|
unmasked = (exceptions & ~fpu_state.cwd) & FPU_CW_Exceptions_Mask;
|
|
|
|
// if IE or DZ exception happen nothing else will be reported
|
|
if (exceptions & (FPU_EX_Invalid | FPU_EX_Zero_Div)) {
|
|
unmasked &= (FPU_EX_Invalid | FPU_EX_Zero_Div);
|
|
}
|
|
|
|
/* Set summary bits if exception isn't masked */
|
|
if (unmasked) {
|
|
fpu_state.swd |= (FPU_SW_Summary | FPU_SW_Backward);
|
|
}
|
|
|
|
if (exceptions & FPU_EX_Invalid) {
|
|
// FPU_EX_Invalid cannot come with any other exception but x87 stack fault
|
|
fpu_state.swd |= exceptions;
|
|
if (exceptions & FPU_SW_Stack_Fault) {
|
|
if (!(exceptions & C1)) {
|
|
/* This bit distinguishes over- from underflow for a stack fault,
|
|
and roundup from round-down for precision loss. */
|
|
fpu_state.swd &= ~C1;
|
|
}
|
|
}
|
|
return unmasked;
|
|
}
|
|
|
|
if (exceptions & FPU_EX_Zero_Div) {
|
|
fpu_state.swd |= FPU_EX_Zero_Div;
|
|
if (!(fpu_state.cwd & FPU_EX_Zero_Div)) {
|
|
#ifdef FPU_8087
|
|
if (!(fpu_state.cwd & FPU_SW_Summary)) {
|
|
fpu_state.cwd |= FPU_SW_Summary;
|
|
nmi = 1;
|
|
}
|
|
#else
|
|
picint(1 << 13);
|
|
#endif // FPU_8087
|
|
}
|
|
return unmasked;
|
|
}
|
|
|
|
if (exceptions & FPU_EX_Denormal) {
|
|
fpu_state.swd |= FPU_EX_Denormal;
|
|
if (unmasked & FPU_EX_Denormal) {
|
|
return (unmasked & FPU_EX_Denormal);
|
|
}
|
|
}
|
|
|
|
/* Set the corresponding exception bits */
|
|
fpu_state.swd |= exceptions;
|
|
|
|
if (exceptions & FPU_EX_Precision) {
|
|
if (!(exceptions & C1)) {
|
|
/* This bit distinguishes over- from underflow for a stack fault,
|
|
and roundup from round-down for precision loss. */
|
|
fpu_state.swd &= ~C1;
|
|
}
|
|
}
|
|
|
|
// If #P unmasked exception occurred the result still has to be
|
|
// written to the destination.
|
|
unmasked &= ~FPU_EX_Precision;
|
|
|
|
if (unmasked & (FPU_EX_Underflow | FPU_EX_Overflow)) {
|
|
// If unmasked over- or underflow occurs and dest is a memory location:
|
|
// - the TOS and destination operands remain unchanged
|
|
// - the inexact-result condition is not reported and C1 flag is cleared
|
|
// - no result is stored in the memory
|
|
// If the destination is in the register stack, adjusted resulting value
|
|
// is stored in the destination operand.
|
|
if (!store)
|
|
unmasked &= ~(FPU_EX_Underflow | FPU_EX_Overflow);
|
|
else {
|
|
fpu_state.swd &= ~C1;
|
|
if (!(status & FPU_EX_Precision))
|
|
fpu_state.swd &= ~FPU_EX_Precision;
|
|
}
|
|
}
|
|
return unmasked;
|
|
}
|
|
|
|
void
|
|
FPU_stack_overflow(uint32_t fetchdat)
|
|
{
|
|
const floatx80 floatx80_default_nan = packFloatx80(0, floatx80_default_nan_exp, floatx80_default_nan_fraction);
|
|
|
|
/* The masked response */
|
|
if (is_IA_masked()) {
|
|
FPU_push();
|
|
FPU_save_regi(floatx80_default_nan, 0);
|
|
}
|
|
FPU_exception(fetchdat, FPU_EX_Stack_Overflow, 0);
|
|
}
|
|
|
|
void
|
|
FPU_stack_underflow(uint32_t fetchdat, int stnr, int pop_stack)
|
|
{
|
|
const floatx80 floatx80_default_nan = packFloatx80(0, floatx80_default_nan_exp, floatx80_default_nan_fraction);
|
|
|
|
/* The masked response */
|
|
if (is_IA_masked()) {
|
|
FPU_save_regi(floatx80_default_nan, stnr);
|
|
if (pop_stack)
|
|
FPU_pop();
|
|
}
|
|
FPU_exception(fetchdat, FPU_EX_Stack_Underflow, 0);
|
|
}
|
|
|
|
/* -----------------------------------------------------------
|
|
* Slimmed down version used to compile against a CPU simulator
|
|
* rather than a kernel (ported by Kevin Lawton)
|
|
* ------------------------------------------------------------ */
|
|
int
|
|
FPU_tagof(const floatx80 reg)
|
|
{
|
|
int32_t exp = floatx80_exp(reg);
|
|
if (exp == 0) {
|
|
if (!floatx80_fraction(reg))
|
|
return X87_TAG_ZERO;
|
|
|
|
/* The number is a de-normal or pseudodenormal. */
|
|
return X87_TAG_INVALID;
|
|
}
|
|
|
|
if (exp == 0x7fff) {
|
|
/* Is an Infinity, a NaN, or an unsupported data type. */
|
|
return X87_TAG_INVALID;
|
|
}
|
|
|
|
if (!(reg.fraction & BX_CONST64(0x8000000000000000))) {
|
|
/* Unsupported data type. */
|
|
/* Valid numbers have the ms bit set to 1. */
|
|
return X87_TAG_INVALID;
|
|
}
|
|
|
|
return X87_TAG_VALID;
|
|
}
|
|
|
|
uint8_t
|
|
pack_FPU_TW(uint16_t twd)
|
|
{
|
|
uint8_t tag_byte = 0;
|
|
|
|
if ((twd & 0x0003) != 0x0003)
|
|
tag_byte |= 0x01;
|
|
if ((twd & 0x000c) != 0x000c)
|
|
tag_byte |= 0x02;
|
|
if ((twd & 0x0030) != 0x0030)
|
|
tag_byte |= 0x04;
|
|
if ((twd & 0x00c0) != 0x00c0)
|
|
tag_byte |= 0x08;
|
|
if ((twd & 0x0300) != 0x0300)
|
|
tag_byte |= 0x10;
|
|
if ((twd & 0x0c00) != 0x0c00)
|
|
tag_byte |= 0x20;
|
|
if ((twd & 0x3000) != 0x3000)
|
|
tag_byte |= 0x40;
|
|
if ((twd & 0xc000) != 0xc000)
|
|
tag_byte |= 0x80;
|
|
|
|
return tag_byte;
|
|
}
|
|
|
|
uint16_t
|
|
unpack_FPU_TW(uint16_t tag_byte)
|
|
{
|
|
uint32_t twd = 0;
|
|
|
|
/* FTW
|
|
*
|
|
* Note that the original format for FTW can be recreated from the stored
|
|
* FTW valid bits and the stored 80-bit FP data (assuming the stored data
|
|
* was not the contents of MMX registers) using the following table:
|
|
|
|
| Exponent | Exponent | Fraction | J,M bits | FTW valid | x87 FTW |
|
|
| all 1s | all 0s | all 0s | | | |
|
|
-------------------------------------------------------------------
|
|
| 0 | 0 | 0 | 0x | 1 | S 10 |
|
|
| 0 | 0 | 0 | 1x | 1 | V 00 |
|
|
-------------------------------------------------------------------
|
|
| 0 | 0 | 1 | 00 | 1 | S 10 |
|
|
| 0 | 0 | 1 | 10 | 1 | V 00 |
|
|
-------------------------------------------------------------------
|
|
| 0 | 1 | 0 | 0x | 1 | S 10 |
|
|
| 0 | 1 | 0 | 1x | 1 | S 10 |
|
|
-------------------------------------------------------------------
|
|
| 0 | 1 | 1 | 00 | 1 | Z 01 |
|
|
| 0 | 1 | 1 | 10 | 1 | S 10 |
|
|
-------------------------------------------------------------------
|
|
| 1 | 0 | 0 | 1x | 1 | S 10 |
|
|
| 1 | 0 | 0 | 1x | 1 | S 10 |
|
|
-------------------------------------------------------------------
|
|
| 1 | 0 | 1 | 00 | 1 | S 10 |
|
|
| 1 | 0 | 1 | 10 | 1 | S 10 |
|
|
-------------------------------------------------------------------
|
|
| all combinations above | 0 | E 11 |
|
|
|
|
*
|
|
* The J-bit is defined to be the 1-bit binary integer to the left of
|
|
* the decimal place in the significand.
|
|
*
|
|
* The M-bit is defined to be the most significant bit of the fractional
|
|
* portion of the significand (i.e., the bit immediately to the right of
|
|
* the decimal place). When the M-bit is the most significant bit of the
|
|
* fractional portion of the significand, it must be 0 if the fraction
|
|
* is all 0's.
|
|
*/
|
|
|
|
for (int index = 7; index >= 0; index--, twd <<= 2, tag_byte <<= 1) {
|
|
if (tag_byte & 0x80) {
|
|
const floatx80 *fpu_reg = &fpu_state.st_space[index & 7];
|
|
twd |= FPU_tagof(*fpu_reg);
|
|
} else {
|
|
twd |= X87_TAG_EMPTY;
|
|
}
|
|
}
|
|
|
|
return (twd >> 2);
|
|
}
|
|
|
|
#ifdef ENABLE_808X_LOG
|
|
void
|
|
x87_dumpregs(void)
|
|
{
|
|
if (cpu_state.ismmx) {
|
|
fpu_log("MM0=%016llX\tMM1=%016llX\tMM2=%016llX\tMM3=%016llX\n", cpu_state.MM[0].q, cpu_state.MM[1].q, cpu_state.MM[2].q, cpu_state.MM[3].q);
|
|
fpu_log("MM4=%016llX\tMM5=%016llX\tMM6=%016llX\tMM7=%016llX\n", cpu_state.MM[4].q, cpu_state.MM[5].q, cpu_state.MM[6].q, cpu_state.MM[7].q);
|
|
} else {
|
|
fpu_log("ST(0)=%f\tST(1)=%f\tST(2)=%f\tST(3)=%f\t\n", cpu_state.ST[cpu_state.TOP], cpu_state.ST[(cpu_state.TOP + 1) & 7], cpu_state.ST[(cpu_state.TOP + 2) & 7], cpu_state.ST[(cpu_state.TOP + 3) & 7]);
|
|
fpu_log("ST(4)=%f\tST(5)=%f\tST(6)=%f\tST(7)=%f\t\n", cpu_state.ST[(cpu_state.TOP + 4) & 7], cpu_state.ST[(cpu_state.TOP + 5) & 7], cpu_state.ST[(cpu_state.TOP + 6) & 7], cpu_state.ST[(cpu_state.TOP + 7) & 7]);
|
|
}
|
|
fpu_log("Status = %04X Control = %04X Tag = %04X\n", cpu_state.npxs, cpu_state.npxc, x87_gettag());
|
|
}
|
|
#endif
|