// 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   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, 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(); 
 | 
} 
 |