mirror of
https://github.com/torvalds/linux.git
synced 2024-12-15 07:33:56 +00:00
b8e4a910e5
Run-tested by booting with "no387 nofxsr" and running test program: [RUN] Testing f[u]comi[p] instructions [OK] f[u]comi[p] Signed-off-by: Denys Vlasenko <dvlasenk@redhat.com> Cc: Andy Lutomirski <luto@amacapital.net> Cc: Borislav Petkov <bp@alien8.de> Cc: Brian Gerst <brgerst@gmail.com> Cc: H. Peter Anvin <hpa@zytor.com> Cc: Kees Cook <keescook@chromium.org> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: linux-kernel@vger.kernel.org Link: http://lkml.kernel.org/r/1442588010-20055-2-git-send-email-dvlasenk@redhat.com Signed-off-by: Ingo Molnar <mingo@kernel.org>
479 lines
10 KiB
C
479 lines
10 KiB
C
/*---------------------------------------------------------------------------+
|
|
| 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 billm@suburbia.net |
|
|
| |
|
|
| |
|
|
+---------------------------------------------------------------------------*/
|
|
|
|
/*---------------------------------------------------------------------------+
|
|
| 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 = 0, 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;
|
|
#ifdef PARANOID
|
|
default:
|
|
EXCEPTION(EX_INTERNAL | 0x121);
|
|
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_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);
|
|
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;
|
|
#ifdef PARANOID
|
|
default:
|
|
EXCEPTION(EX_INTERNAL | 0x122);
|
|
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_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;
|
|
#ifdef PARANOID
|
|
default:
|
|
EXCEPTION(EX_INTERNAL | 0x122);
|
|
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;
|
|
}
|
|
|
|
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();
|
|
}
|