mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
479 lines
10 KiB
479 lines
10 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/*---------------------------------------------------------------------------+ |
|
| reg_compare.c | |
|
| | |
|
| Compare two floating point registers | |
|
| | |
|
| Copyright (C) 1992,1993,1994,1997 | |
|
| W. Metzenthen, 22 Parker St, Ormond, Vic 3163, Australia | |
|
| E-mail [email protected] | |
|
| | |
|
| | |
|
+---------------------------------------------------------------------------*/ |
|
|
|
/*---------------------------------------------------------------------------+ |
|
| compare() is the core FPU_REG comparison function | |
|
+---------------------------------------------------------------------------*/ |
|
|
|
#include "fpu_system.h" |
|
#include "exception.h" |
|
#include "fpu_emu.h" |
|
#include "control_w.h" |
|
#include "status_w.h" |
|
|
|
static int compare(FPU_REG const *b, int tagb) |
|
{ |
|
int diff, exp0, expb; |
|
u_char st0_tag; |
|
FPU_REG *st0_ptr; |
|
FPU_REG x, y; |
|
u_char st0_sign, signb = getsign(b); |
|
|
|
st0_ptr = &st(0); |
|
st0_tag = FPU_gettag0(); |
|
st0_sign = getsign(st0_ptr); |
|
|
|
if (tagb == TAG_Special) |
|
tagb = FPU_Special(b); |
|
if (st0_tag == TAG_Special) |
|
st0_tag = FPU_Special(st0_ptr); |
|
|
|
if (((st0_tag != TAG_Valid) && (st0_tag != TW_Denormal)) |
|
|| ((tagb != TAG_Valid) && (tagb != TW_Denormal))) { |
|
if (st0_tag == TAG_Zero) { |
|
if (tagb == TAG_Zero) |
|
return COMP_A_eq_B; |
|
if (tagb == TAG_Valid) |
|
return ((signb == |
|
SIGN_POS) ? COMP_A_lt_B : COMP_A_gt_B); |
|
if (tagb == TW_Denormal) |
|
return ((signb == |
|
SIGN_POS) ? COMP_A_lt_B : COMP_A_gt_B) |
|
| COMP_Denormal; |
|
} else if (tagb == TAG_Zero) { |
|
if (st0_tag == TAG_Valid) |
|
return ((st0_sign == |
|
SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B); |
|
if (st0_tag == TW_Denormal) |
|
return ((st0_sign == |
|
SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B) |
|
| COMP_Denormal; |
|
} |
|
|
|
if (st0_tag == TW_Infinity) { |
|
if ((tagb == TAG_Valid) || (tagb == TAG_Zero)) |
|
return ((st0_sign == |
|
SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B); |
|
else if (tagb == TW_Denormal) |
|
return ((st0_sign == |
|
SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B) |
|
| COMP_Denormal; |
|
else if (tagb == TW_Infinity) { |
|
/* The 80486 book says that infinities can be equal! */ |
|
return (st0_sign == signb) ? COMP_A_eq_B : |
|
((st0_sign == |
|
SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B); |
|
} |
|
/* Fall through to the NaN code */ |
|
} else if (tagb == TW_Infinity) { |
|
if ((st0_tag == TAG_Valid) || (st0_tag == TAG_Zero)) |
|
return ((signb == |
|
SIGN_POS) ? COMP_A_lt_B : COMP_A_gt_B); |
|
if (st0_tag == TW_Denormal) |
|
return ((signb == |
|
SIGN_POS) ? COMP_A_lt_B : COMP_A_gt_B) |
|
| COMP_Denormal; |
|
/* Fall through to the NaN code */ |
|
} |
|
|
|
/* The only possibility now should be that one of the arguments |
|
is a NaN */ |
|
if ((st0_tag == TW_NaN) || (tagb == TW_NaN)) { |
|
int signalling = 0, unsupported = 0; |
|
if (st0_tag == TW_NaN) { |
|
signalling = |
|
(st0_ptr->sigh & 0xc0000000) == 0x80000000; |
|
unsupported = !((exponent(st0_ptr) == EXP_OVER) |
|
&& (st0_ptr-> |
|
sigh & 0x80000000)); |
|
} |
|
if (tagb == TW_NaN) { |
|
signalling |= |
|
(b->sigh & 0xc0000000) == 0x80000000; |
|
unsupported |= !((exponent(b) == EXP_OVER) |
|
&& (b->sigh & 0x80000000)); |
|
} |
|
if (signalling || unsupported) |
|
return COMP_No_Comp | COMP_SNaN | COMP_NaN; |
|
else |
|
/* Neither is a signaling NaN */ |
|
return COMP_No_Comp | COMP_NaN; |
|
} |
|
|
|
EXCEPTION(EX_Invalid); |
|
} |
|
|
|
if (st0_sign != signb) { |
|
return ((st0_sign == SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B) |
|
| (((st0_tag == TW_Denormal) || (tagb == TW_Denormal)) ? |
|
COMP_Denormal : 0); |
|
} |
|
|
|
if ((st0_tag == TW_Denormal) || (tagb == TW_Denormal)) { |
|
FPU_to_exp16(st0_ptr, &x); |
|
FPU_to_exp16(b, &y); |
|
st0_ptr = &x; |
|
b = &y; |
|
exp0 = exponent16(st0_ptr); |
|
expb = exponent16(b); |
|
} else { |
|
exp0 = exponent(st0_ptr); |
|
expb = exponent(b); |
|
} |
|
|
|
#ifdef PARANOID |
|
if (!(st0_ptr->sigh & 0x80000000)) |
|
EXCEPTION(EX_Invalid); |
|
if (!(b->sigh & 0x80000000)) |
|
EXCEPTION(EX_Invalid); |
|
#endif /* PARANOID */ |
|
|
|
diff = exp0 - expb; |
|
if (diff == 0) { |
|
diff = st0_ptr->sigh - b->sigh; /* Works only if ms bits are |
|
identical */ |
|
if (diff == 0) { |
|
diff = st0_ptr->sigl > b->sigl; |
|
if (diff == 0) |
|
diff = -(st0_ptr->sigl < b->sigl); |
|
} |
|
} |
|
|
|
if (diff > 0) { |
|
return ((st0_sign == SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B) |
|
| (((st0_tag == TW_Denormal) || (tagb == TW_Denormal)) ? |
|
COMP_Denormal : 0); |
|
} |
|
if (diff < 0) { |
|
return ((st0_sign == SIGN_POS) ? COMP_A_lt_B : COMP_A_gt_B) |
|
| (((st0_tag == TW_Denormal) || (tagb == TW_Denormal)) ? |
|
COMP_Denormal : 0); |
|
} |
|
|
|
return COMP_A_eq_B |
|
| (((st0_tag == TW_Denormal) || (tagb == TW_Denormal)) ? |
|
COMP_Denormal : 0); |
|
|
|
} |
|
|
|
/* This function requires that st(0) is not empty */ |
|
int FPU_compare_st_data(FPU_REG const *loaded_data, u_char loaded_tag) |
|
{ |
|
int f, c; |
|
|
|
c = compare(loaded_data, loaded_tag); |
|
|
|
if (c & COMP_NaN) { |
|
EXCEPTION(EX_Invalid); |
|
f = SW_C3 | SW_C2 | SW_C0; |
|
} else |
|
switch (c & 7) { |
|
case COMP_A_lt_B: |
|
f = SW_C0; |
|
break; |
|
case COMP_A_eq_B: |
|
f = SW_C3; |
|
break; |
|
case COMP_A_gt_B: |
|
f = 0; |
|
break; |
|
case COMP_No_Comp: |
|
f = SW_C3 | SW_C2 | SW_C0; |
|
break; |
|
default: |
|
#ifdef PARANOID |
|
EXCEPTION(EX_INTERNAL | 0x121); |
|
#endif /* PARANOID */ |
|
f = SW_C3 | SW_C2 | SW_C0; |
|
break; |
|
} |
|
setcc(f); |
|
if (c & COMP_Denormal) { |
|
return denormal_operand() < 0; |
|
} |
|
return 0; |
|
} |
|
|
|
static int compare_st_st(int nr) |
|
{ |
|
int f, c; |
|
FPU_REG *st_ptr; |
|
|
|
if (!NOT_EMPTY(0) || !NOT_EMPTY(nr)) { |
|
setcc(SW_C3 | SW_C2 | SW_C0); |
|
/* Stack fault */ |
|
EXCEPTION(EX_StackUnder); |
|
return !(control_word & CW_Invalid); |
|
} |
|
|
|
st_ptr = &st(nr); |
|
c = compare(st_ptr, FPU_gettagi(nr)); |
|
if (c & COMP_NaN) { |
|
setcc(SW_C3 | SW_C2 | SW_C0); |
|
EXCEPTION(EX_Invalid); |
|
return !(control_word & CW_Invalid); |
|
} else |
|
switch (c & 7) { |
|
case COMP_A_lt_B: |
|
f = SW_C0; |
|
break; |
|
case COMP_A_eq_B: |
|
f = SW_C3; |
|
break; |
|
case COMP_A_gt_B: |
|
f = 0; |
|
break; |
|
case COMP_No_Comp: |
|
f = SW_C3 | SW_C2 | SW_C0; |
|
break; |
|
default: |
|
#ifdef PARANOID |
|
EXCEPTION(EX_INTERNAL | 0x122); |
|
#endif /* PARANOID */ |
|
f = SW_C3 | SW_C2 | SW_C0; |
|
break; |
|
} |
|
setcc(f); |
|
if (c & COMP_Denormal) { |
|
return denormal_operand() < 0; |
|
} |
|
return 0; |
|
} |
|
|
|
static int compare_i_st_st(int nr) |
|
{ |
|
int f, c; |
|
FPU_REG *st_ptr; |
|
|
|
if (!NOT_EMPTY(0) || !NOT_EMPTY(nr)) { |
|
FPU_EFLAGS |= (X86_EFLAGS_ZF | X86_EFLAGS_PF | X86_EFLAGS_CF); |
|
/* Stack fault */ |
|
EXCEPTION(EX_StackUnder); |
|
return !(control_word & CW_Invalid); |
|
} |
|
|
|
partial_status &= ~SW_C0; |
|
st_ptr = &st(nr); |
|
c = compare(st_ptr, FPU_gettagi(nr)); |
|
if (c & COMP_NaN) { |
|
FPU_EFLAGS |= (X86_EFLAGS_ZF | X86_EFLAGS_PF | X86_EFLAGS_CF); |
|
EXCEPTION(EX_Invalid); |
|
return !(control_word & CW_Invalid); |
|
} |
|
|
|
switch (c & 7) { |
|
case COMP_A_lt_B: |
|
f = X86_EFLAGS_CF; |
|
break; |
|
case COMP_A_eq_B: |
|
f = X86_EFLAGS_ZF; |
|
break; |
|
case COMP_A_gt_B: |
|
f = 0; |
|
break; |
|
case COMP_No_Comp: |
|
f = X86_EFLAGS_ZF | X86_EFLAGS_PF | X86_EFLAGS_CF; |
|
break; |
|
default: |
|
#ifdef PARANOID |
|
EXCEPTION(EX_INTERNAL | 0x122); |
|
#endif /* PARANOID */ |
|
f = 0; |
|
break; |
|
} |
|
FPU_EFLAGS = (FPU_EFLAGS & ~(X86_EFLAGS_ZF | X86_EFLAGS_PF | X86_EFLAGS_CF)) | f; |
|
if (c & COMP_Denormal) { |
|
return denormal_operand() < 0; |
|
} |
|
return 0; |
|
} |
|
|
|
static int compare_u_st_st(int nr) |
|
{ |
|
int f = 0, c; |
|
FPU_REG *st_ptr; |
|
|
|
if (!NOT_EMPTY(0) || !NOT_EMPTY(nr)) { |
|
setcc(SW_C3 | SW_C2 | SW_C0); |
|
/* Stack fault */ |
|
EXCEPTION(EX_StackUnder); |
|
return !(control_word & CW_Invalid); |
|
} |
|
|
|
st_ptr = &st(nr); |
|
c = compare(st_ptr, FPU_gettagi(nr)); |
|
if (c & COMP_NaN) { |
|
setcc(SW_C3 | SW_C2 | SW_C0); |
|
if (c & COMP_SNaN) { /* This is the only difference between |
|
un-ordered and ordinary comparisons */ |
|
EXCEPTION(EX_Invalid); |
|
return !(control_word & CW_Invalid); |
|
} |
|
return 0; |
|
} else |
|
switch (c & 7) { |
|
case COMP_A_lt_B: |
|
f = SW_C0; |
|
break; |
|
case COMP_A_eq_B: |
|
f = SW_C3; |
|
break; |
|
case COMP_A_gt_B: |
|
f = 0; |
|
break; |
|
case COMP_No_Comp: |
|
f = SW_C3 | SW_C2 | SW_C0; |
|
break; |
|
#ifdef PARANOID |
|
default: |
|
EXCEPTION(EX_INTERNAL | 0x123); |
|
f = SW_C3 | SW_C2 | SW_C0; |
|
break; |
|
#endif /* PARANOID */ |
|
} |
|
setcc(f); |
|
if (c & COMP_Denormal) { |
|
return denormal_operand() < 0; |
|
} |
|
return 0; |
|
} |
|
|
|
static int compare_ui_st_st(int nr) |
|
{ |
|
int f = 0, c; |
|
FPU_REG *st_ptr; |
|
|
|
if (!NOT_EMPTY(0) || !NOT_EMPTY(nr)) { |
|
FPU_EFLAGS |= (X86_EFLAGS_ZF | X86_EFLAGS_PF | X86_EFLAGS_CF); |
|
/* Stack fault */ |
|
EXCEPTION(EX_StackUnder); |
|
return !(control_word & CW_Invalid); |
|
} |
|
|
|
partial_status &= ~SW_C0; |
|
st_ptr = &st(nr); |
|
c = compare(st_ptr, FPU_gettagi(nr)); |
|
if (c & COMP_NaN) { |
|
FPU_EFLAGS |= (X86_EFLAGS_ZF | X86_EFLAGS_PF | X86_EFLAGS_CF); |
|
if (c & COMP_SNaN) { /* This is the only difference between |
|
un-ordered and ordinary comparisons */ |
|
EXCEPTION(EX_Invalid); |
|
return !(control_word & CW_Invalid); |
|
} |
|
return 0; |
|
} |
|
|
|
switch (c & 7) { |
|
case COMP_A_lt_B: |
|
f = X86_EFLAGS_CF; |
|
break; |
|
case COMP_A_eq_B: |
|
f = X86_EFLAGS_ZF; |
|
break; |
|
case COMP_A_gt_B: |
|
f = 0; |
|
break; |
|
case COMP_No_Comp: |
|
f = X86_EFLAGS_ZF | X86_EFLAGS_PF | X86_EFLAGS_CF; |
|
break; |
|
#ifdef PARANOID |
|
default: |
|
EXCEPTION(EX_INTERNAL | 0x123); |
|
f = 0; |
|
break; |
|
#endif /* PARANOID */ |
|
} |
|
FPU_EFLAGS = (FPU_EFLAGS & ~(X86_EFLAGS_ZF | X86_EFLAGS_PF | X86_EFLAGS_CF)) | f; |
|
if (c & COMP_Denormal) { |
|
return denormal_operand() < 0; |
|
} |
|
return 0; |
|
} |
|
|
|
/*---------------------------------------------------------------------------*/ |
|
|
|
void fcom_st(void) |
|
{ |
|
/* fcom st(i) */ |
|
compare_st_st(FPU_rm); |
|
} |
|
|
|
void fcompst(void) |
|
{ |
|
/* fcomp st(i) */ |
|
if (!compare_st_st(FPU_rm)) |
|
FPU_pop(); |
|
} |
|
|
|
void fcompp(void) |
|
{ |
|
/* fcompp */ |
|
if (FPU_rm != 1) { |
|
FPU_illegal(); |
|
return; |
|
} |
|
if (!compare_st_st(1)) |
|
poppop(); |
|
} |
|
|
|
void fucom_(void) |
|
{ |
|
/* fucom st(i) */ |
|
compare_u_st_st(FPU_rm); |
|
|
|
} |
|
|
|
void fucomp(void) |
|
{ |
|
/* fucomp st(i) */ |
|
if (!compare_u_st_st(FPU_rm)) |
|
FPU_pop(); |
|
} |
|
|
|
void fucompp(void) |
|
{ |
|
/* fucompp */ |
|
if (FPU_rm == 1) { |
|
if (!compare_u_st_st(1)) |
|
poppop(); |
|
} else |
|
FPU_illegal(); |
|
} |
|
|
|
/* P6+ compare-to-EFLAGS ops */ |
|
|
|
void fcomi_(void) |
|
{ |
|
/* fcomi st(i) */ |
|
compare_i_st_st(FPU_rm); |
|
} |
|
|
|
void fcomip(void) |
|
{ |
|
/* fcomip st(i) */ |
|
if (!compare_i_st_st(FPU_rm)) |
|
FPU_pop(); |
|
} |
|
|
|
void fucomi_(void) |
|
{ |
|
/* fucomi st(i) */ |
|
compare_ui_st_st(FPU_rm); |
|
} |
|
|
|
void fucomip(void) |
|
{ |
|
/* fucomip st(i) */ |
|
if (!compare_ui_st_st(FPU_rm)) |
|
FPU_pop(); |
|
}
|
|
|