root/arch/arm/nwfpe/fpa11_cprt.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
    NetWinder Floating Point Emulator
    (c) Rebel.COM, 1998,1999
    (c) Philip Blundell, 1999, 2001

    Direct questions, comments to Scott Bambrough <scottb@netwinder.org>

*/

#include "fpa11.h"
#include "fpopcode.h"
#include "fpa11.inl"
#include "fpmodule.h"
#include "fpmodule.inl"
#include "softfloat.h"

unsigned int PerformFLT(const unsigned int opcode);
unsigned int PerformFIX(const unsigned int opcode);

static unsigned int PerformComparison(const unsigned int opcode);

unsigned int EmulateCPRT(const unsigned int opcode)
{

        if (opcode & 0x800000) {
                /* This is some variant of a comparison (PerformComparison
                   will sort out which one).  Since most of the other CPRT
                   instructions are oddball cases of some sort or other it
                   makes sense to pull this out into a fast path.  */
                return PerformComparison(opcode);
        }

        /* Hint to GCC that we'd like a jump table rather than a load of CMPs */
        switch ((opcode & 0x700000) >> 20) {
        case FLT_CODE >> 20:
                return PerformFLT(opcode);
                break;
        case FIX_CODE >> 20:
                return PerformFIX(opcode);
                break;

        case WFS_CODE >> 20:
                writeFPSR(readRegister(getRd(opcode)));
                break;
        case RFS_CODE >> 20:
                writeRegister(getRd(opcode), readFPSR());
                break;

        default:
                return 0;
        }

        return 1;
}

unsigned int PerformFLT(const unsigned int opcode)
{
        FPA11 *fpa11 = GET_FPA11();
        struct roundingData roundData;

        roundData.mode = SetRoundingMode(opcode);
        roundData.precision = SetRoundingPrecision(opcode);
        roundData.exception = 0;

        switch (opcode & MASK_ROUNDING_PRECISION) {
        case ROUND_SINGLE:
                {
                        fpa11->fType[getFn(opcode)] = typeSingle;
                        fpa11->fpreg[getFn(opcode)].fSingle = int32_to_float32(&roundData, readRegister(getRd(opcode)));
                }
                break;

        case ROUND_DOUBLE:
                {
                        fpa11->fType[getFn(opcode)] = typeDouble;
                        fpa11->fpreg[getFn(opcode)].fDouble = int32_to_float64(readRegister(getRd(opcode)));
                }
                break;

#ifdef CONFIG_FPE_NWFPE_XP
        case ROUND_EXTENDED:
                {
                        fpa11->fType[getFn(opcode)] = typeExtended;
                        fpa11->fpreg[getFn(opcode)].fExtended = int32_to_floatx80(readRegister(getRd(opcode)));
                }
                break;
#endif

        default:
                return 0;
        }

        if (roundData.exception)
                float_raise(roundData.exception);

        return 1;
}

unsigned int PerformFIX(const unsigned int opcode)
{
        FPA11 *fpa11 = GET_FPA11();
        unsigned int Fn = getFm(opcode);
        struct roundingData roundData;

        roundData.mode = SetRoundingMode(opcode);
        roundData.precision = SetRoundingPrecision(opcode);
        roundData.exception = 0;

        switch (fpa11->fType[Fn]) {
        case typeSingle:
                {
                        writeRegister(getRd(opcode), float32_to_int32(&roundData, fpa11->fpreg[Fn].fSingle));
                }
                break;

        case typeDouble:
                {
                        writeRegister(getRd(opcode), float64_to_int32(&roundData, fpa11->fpreg[Fn].fDouble));
                }
                break;

#ifdef CONFIG_FPE_NWFPE_XP
        case typeExtended:
                {
                        writeRegister(getRd(opcode), floatx80_to_int32(&roundData, fpa11->fpreg[Fn].fExtended));
                }
                break;
#endif

        default:
                return 0;
        }

        if (roundData.exception)
                float_raise(roundData.exception);

        return 1;
}

/* This instruction sets the flags N, Z, C, V in the FPSR. */
static unsigned int PerformComparison(const unsigned int opcode)
{
        FPA11 *fpa11 = GET_FPA11();
        unsigned int Fn = getFn(opcode), Fm = getFm(opcode);
        int e_flag = opcode & 0x400000; /* 1 if CxFE */
        int n_flag = opcode & 0x200000; /* 1 if CNxx */
        unsigned int flags = 0;

#ifdef CONFIG_FPE_NWFPE_XP
        floatx80 rFn, rFm;

        /* Check for unordered condition and convert all operands to 80-bit
           format.
           ?? Might be some mileage in avoiding this conversion if possible.
           Eg, if both operands are 32-bit, detect this and do a 32-bit
           comparison (cheaper than an 80-bit one).  */
        switch (fpa11->fType[Fn]) {
        case typeSingle:
                //printk("single.\n");
                if (float32_is_nan(fpa11->fpreg[Fn].fSingle))
                        goto unordered;
                rFn = float32_to_floatx80(fpa11->fpreg[Fn].fSingle);
                break;

        case typeDouble:
                //printk("double.\n");
                if (float64_is_nan(fpa11->fpreg[Fn].fDouble))
                        goto unordered;
                rFn = float64_to_floatx80(fpa11->fpreg[Fn].fDouble);
                break;

        case typeExtended:
                //printk("extended.\n");
                if (floatx80_is_nan(fpa11->fpreg[Fn].fExtended))
                        goto unordered;
                rFn = fpa11->fpreg[Fn].fExtended;
                break;

        default:
                return 0;
        }

        if (CONSTANT_FM(opcode)) {
                //printk("Fm is a constant: #%d.\n",Fm);
                rFm = getExtendedConstant(Fm);
                if (floatx80_is_nan(rFm))
                        goto unordered;
        } else {
                //printk("Fm = r%d which contains a ",Fm);
                switch (fpa11->fType[Fm]) {
                case typeSingle:
                        //printk("single.\n");
                        if (float32_is_nan(fpa11->fpreg[Fm].fSingle))
                                goto unordered;
                        rFm = float32_to_floatx80(fpa11->fpreg[Fm].fSingle);
                        break;

                case typeDouble:
                        //printk("double.\n");
                        if (float64_is_nan(fpa11->fpreg[Fm].fDouble))
                                goto unordered;
                        rFm = float64_to_floatx80(fpa11->fpreg[Fm].fDouble);
                        break;

                case typeExtended:
                        //printk("extended.\n");
                        if (floatx80_is_nan(fpa11->fpreg[Fm].fExtended))
                                goto unordered;
                        rFm = fpa11->fpreg[Fm].fExtended;
                        break;

                default:
                        return 0;
                }
        }

        if (n_flag)
                rFm.high ^= 0x8000;

        /* test for less than condition */
        if (floatx80_lt(rFn, rFm))
                flags |= CC_NEGATIVE;

        /* test for equal condition */
        if (floatx80_eq(rFn, rFm))
                flags |= CC_ZERO;

        /* test for greater than or equal condition */
        if (floatx80_lt(rFm, rFn))
                flags |= CC_CARRY;

#else
        if (CONSTANT_FM(opcode)) {
                /* Fm is a constant.  Do the comparison in whatever precision
                   Fn happens to be stored in.  */
                if (fpa11->fType[Fn] == typeSingle) {
                        float32 rFm = getSingleConstant(Fm);
                        float32 rFn = fpa11->fpreg[Fn].fSingle;

                        if (float32_is_nan(rFn))
                                goto unordered;

                        if (n_flag)
                                rFm ^= 0x80000000;

                        /* test for less than condition */
                        if (float32_lt_nocheck(rFn, rFm))
                                flags |= CC_NEGATIVE;

                        /* test for equal condition */
                        if (float32_eq_nocheck(rFn, rFm))
                                flags |= CC_ZERO;

                        /* test for greater than or equal condition */
                        if (float32_lt_nocheck(rFm, rFn))
                                flags |= CC_CARRY;
                } else {
                        float64 rFm = getDoubleConstant(Fm);
                        float64 rFn = fpa11->fpreg[Fn].fDouble;

                        if (float64_is_nan(rFn))
                                goto unordered;

                        if (n_flag)
                                rFm ^= 0x8000000000000000ULL;

                        /* test for less than condition */
                        if (float64_lt_nocheck(rFn, rFm))
                                flags |= CC_NEGATIVE;

                        /* test for equal condition */
                        if (float64_eq_nocheck(rFn, rFm))
                                flags |= CC_ZERO;

                        /* test for greater than or equal condition */
                        if (float64_lt_nocheck(rFm, rFn))
                                flags |= CC_CARRY;
                }
        } else {
                /* Both operands are in registers.  */
                if (fpa11->fType[Fn] == typeSingle
                    && fpa11->fType[Fm] == typeSingle) {
                        float32 rFm = fpa11->fpreg[Fm].fSingle;
                        float32 rFn = fpa11->fpreg[Fn].fSingle;

                        if (float32_is_nan(rFn)
                            || float32_is_nan(rFm))
                                goto unordered;

                        if (n_flag)
                                rFm ^= 0x80000000;

                        /* test for less than condition */
                        if (float32_lt_nocheck(rFn, rFm))
                                flags |= CC_NEGATIVE;

                        /* test for equal condition */
                        if (float32_eq_nocheck(rFn, rFm))
                                flags |= CC_ZERO;

                        /* test for greater than or equal condition */
                        if (float32_lt_nocheck(rFm, rFn))
                                flags |= CC_CARRY;
                } else {
                        /* Promote 32-bit operand to 64 bits.  */
                        float64 rFm, rFn;

                        rFm = (fpa11->fType[Fm] == typeSingle) ?
                            float32_to_float64(fpa11->fpreg[Fm].fSingle)
                            : fpa11->fpreg[Fm].fDouble;

                        rFn = (fpa11->fType[Fn] == typeSingle) ?
                            float32_to_float64(fpa11->fpreg[Fn].fSingle)
                            : fpa11->fpreg[Fn].fDouble;

                        if (float64_is_nan(rFn)
                            || float64_is_nan(rFm))
                                goto unordered;

                        if (n_flag)
                                rFm ^= 0x8000000000000000ULL;

                        /* test for less than condition */
                        if (float64_lt_nocheck(rFn, rFm))
                                flags |= CC_NEGATIVE;

                        /* test for equal condition */
                        if (float64_eq_nocheck(rFn, rFm))
                                flags |= CC_ZERO;

                        /* test for greater than or equal condition */
                        if (float64_lt_nocheck(rFm, rFn))
                                flags |= CC_CARRY;
                }
        }

#endif

        writeConditionCodes(flags);

        return 1;

      unordered:
        /* ?? The FPA data sheet is pretty vague about this, in particular
           about whether the non-E comparisons can ever raise exceptions.
           This implementation is based on a combination of what it says in
           the data sheet, observation of how the Acorn emulator actually
           behaves (and how programs expect it to) and guesswork.  */
        flags |= CC_OVERFLOW;
        flags &= ~(CC_ZERO | CC_NEGATIVE);

        if (BIT_AC & readFPSR())
                flags |= CC_CARRY;

        if (e_flag)
                float_raise(float_flag_invalid);

        writeConditionCodes(flags);
        return 1;
}