linux/arch/x86/math-emu/reg_add_sub.c
<<
>>
Prefs
   1/*---------------------------------------------------------------------------+
   2 |  reg_add_sub.c                                                            |
   3 |                                                                           |
   4 | Functions to add or subtract two registers and put the result in a third. |
   5 |                                                                           |
   6 | Copyright (C) 1992,1993,1997                                              |
   7 |                  W. Metzenthen, 22 Parker St, Ormond, Vic 3163, Australia |
   8 |                  E-mail   billm@suburbia.net                              |
   9 |                                                                           |
  10 |                                                                           |
  11 +---------------------------------------------------------------------------*/
  12
  13/*---------------------------------------------------------------------------+
  14 |  For each function, the destination may be any FPU_REG, including one of  |
  15 | the source FPU_REGs.                                                      |
  16 |  Each function returns 0 if the answer is o.k., otherwise a non-zero      |
  17 | value is returned, indicating either an exception condition or an         |
  18 | internal error.                                                           |
  19 +---------------------------------------------------------------------------*/
  20
  21#include "exception.h"
  22#include "reg_constant.h"
  23#include "fpu_emu.h"
  24#include "control_w.h"
  25#include "fpu_system.h"
  26
  27static
  28int add_sub_specials(FPU_REG const *a, u_char taga, u_char signa,
  29                     FPU_REG const *b, u_char tagb, u_char signb,
  30                     FPU_REG * dest, int deststnr, int control_w);
  31
  32/*
  33  Operates on st(0) and st(n), or on st(0) and temporary data.
  34  The destination must be one of the source st(x).
  35  */
  36int FPU_add(FPU_REG const *b, u_char tagb, int deststnr, int control_w)
  37{
  38        FPU_REG *a = &st(0);
  39        FPU_REG *dest = &st(deststnr);
  40        u_char signb = getsign(b);
  41        u_char taga = FPU_gettag0();
  42        u_char signa = getsign(a);
  43        u_char saved_sign = getsign(dest);
  44        int diff, tag, expa, expb;
  45
  46        if (!(taga | tagb)) {
  47                expa = exponent(a);
  48                expb = exponent(b);
  49
  50              valid_add:
  51                /* Both registers are valid */
  52                if (!(signa ^ signb)) {
  53                        /* signs are the same */
  54                        tag =
  55                            FPU_u_add(a, b, dest, control_w, signa, expa, expb);
  56                } else {
  57                        /* The signs are different, so do a subtraction */
  58                        diff = expa - expb;
  59                        if (!diff) {
  60                                diff = a->sigh - b->sigh;       /* This works only if the ms bits
  61                                                                   are identical. */
  62                                if (!diff) {
  63                                        diff = a->sigl > b->sigl;
  64                                        if (!diff)
  65                                                diff = -(a->sigl < b->sigl);
  66                                }
  67                        }
  68
  69                        if (diff > 0) {
  70                                tag =
  71                                    FPU_u_sub(a, b, dest, control_w, signa,
  72                                              expa, expb);
  73                        } else if (diff < 0) {
  74                                tag =
  75                                    FPU_u_sub(b, a, dest, control_w, signb,
  76                                              expb, expa);
  77                        } else {
  78                                FPU_copy_to_regi(&CONST_Z, TAG_Zero, deststnr);
  79                                /* sign depends upon rounding mode */
  80                                setsign(dest, ((control_w & CW_RC) != RC_DOWN)
  81                                        ? SIGN_POS : SIGN_NEG);
  82                                return TAG_Zero;
  83                        }
  84                }
  85
  86                if (tag < 0) {
  87                        setsign(dest, saved_sign);
  88                        return tag;
  89                }
  90                FPU_settagi(deststnr, tag);
  91                return tag;
  92        }
  93
  94        if (taga == TAG_Special)
  95                taga = FPU_Special(a);
  96        if (tagb == TAG_Special)
  97                tagb = FPU_Special(b);
  98
  99        if (((taga == TAG_Valid) && (tagb == TW_Denormal))
 100            || ((taga == TW_Denormal) && (tagb == TAG_Valid))
 101            || ((taga == TW_Denormal) && (tagb == TW_Denormal))) {
 102                FPU_REG x, y;
 103
 104                if (denormal_operand() < 0)
 105                        return FPU_Exception;
 106
 107                FPU_to_exp16(a, &x);
 108                FPU_to_exp16(b, &y);
 109                a = &x;
 110                b = &y;
 111                expa = exponent16(a);
 112                expb = exponent16(b);
 113                goto valid_add;
 114        }
 115
 116        if ((taga == TW_NaN) || (tagb == TW_NaN)) {
 117                if (deststnr == 0)
 118                        return real_2op_NaN(b, tagb, deststnr, a);
 119                else
 120                        return real_2op_NaN(a, taga, deststnr, a);
 121        }
 122
 123        return add_sub_specials(a, taga, signa, b, tagb, signb,
 124                                dest, deststnr, control_w);
 125}
 126
 127/* Subtract b from a.  (a-b) -> dest */
 128int FPU_sub(int flags, int rm, int control_w)
 129{
 130        FPU_REG const *a, *b;
 131        FPU_REG *dest;
 132        u_char taga, tagb, signa, signb, saved_sign, sign;
 133        int diff, tag = 0, expa, expb, deststnr;
 134
 135        a = &st(0);
 136        taga = FPU_gettag0();
 137
 138        deststnr = 0;
 139        if (flags & LOADED) {
 140                b = (FPU_REG *) rm;
 141                tagb = flags & 0x0f;
 142        } else {
 143                b = &st(rm);
 144                tagb = FPU_gettagi(rm);
 145
 146                if (flags & DEST_RM)
 147                        deststnr = rm;
 148        }
 149
 150        signa = getsign(a);
 151        signb = getsign(b);
 152
 153        if (flags & REV) {
 154                signa ^= SIGN_NEG;
 155                signb ^= SIGN_NEG;
 156        }
 157
 158        dest = &st(deststnr);
 159        saved_sign = getsign(dest);
 160
 161        if (!(taga | tagb)) {
 162                expa = exponent(a);
 163                expb = exponent(b);
 164
 165              valid_subtract:
 166                /* Both registers are valid */
 167
 168                diff = expa - expb;
 169
 170                if (!diff) {
 171                        diff = a->sigh - b->sigh;       /* Works only if ms bits are identical */
 172                        if (!diff) {
 173                                diff = a->sigl > b->sigl;
 174                                if (!diff)
 175                                        diff = -(a->sigl < b->sigl);
 176                        }
 177                }
 178
 179                switch ((((int)signa) * 2 + signb) / SIGN_NEG) {
 180                case 0: /* P - P */
 181                case 3: /* N - N */
 182                        if (diff > 0) {
 183                                /* |a| > |b| */
 184                                tag =
 185                                    FPU_u_sub(a, b, dest, control_w, signa,
 186                                              expa, expb);
 187                        } else if (diff == 0) {
 188                                FPU_copy_to_regi(&CONST_Z, TAG_Zero, deststnr);
 189
 190                                /* sign depends upon rounding mode */
 191                                setsign(dest, ((control_w & CW_RC) != RC_DOWN)
 192                                        ? SIGN_POS : SIGN_NEG);
 193                                return TAG_Zero;
 194                        } else {
 195                                sign = signa ^ SIGN_NEG;
 196                                tag =
 197                                    FPU_u_sub(b, a, dest, control_w, sign, expb,
 198                                              expa);
 199                        }
 200                        break;
 201                case 1: /* P - N */
 202                        tag =
 203                            FPU_u_add(a, b, dest, control_w, SIGN_POS, expa,
 204                                      expb);
 205                        break;
 206                case 2: /* N - P */
 207                        tag =
 208                            FPU_u_add(a, b, dest, control_w, SIGN_NEG, expa,
 209                                      expb);
 210                        break;
 211#ifdef PARANOID
 212                default:
 213                        EXCEPTION(EX_INTERNAL | 0x111);
 214                        return -1;
 215#endif
 216                }
 217                if (tag < 0) {
 218                        setsign(dest, saved_sign);
 219                        return tag;
 220                }
 221                FPU_settagi(deststnr, tag);
 222                return tag;
 223        }
 224
 225        if (taga == TAG_Special)
 226                taga = FPU_Special(a);
 227        if (tagb == TAG_Special)
 228                tagb = FPU_Special(b);
 229
 230        if (((taga == TAG_Valid) && (tagb == TW_Denormal))
 231            || ((taga == TW_Denormal) && (tagb == TAG_Valid))
 232            || ((taga == TW_Denormal) && (tagb == TW_Denormal))) {
 233                FPU_REG x, y;
 234
 235                if (denormal_operand() < 0)
 236                        return FPU_Exception;
 237
 238                FPU_to_exp16(a, &x);
 239                FPU_to_exp16(b, &y);
 240                a = &x;
 241                b = &y;
 242                expa = exponent16(a);
 243                expb = exponent16(b);
 244
 245                goto valid_subtract;
 246        }
 247
 248        if ((taga == TW_NaN) || (tagb == TW_NaN)) {
 249                FPU_REG const *d1, *d2;
 250                if (flags & REV) {
 251                        d1 = b;
 252                        d2 = a;
 253                } else {
 254                        d1 = a;
 255                        d2 = b;
 256                }
 257                if (flags & LOADED)
 258                        return real_2op_NaN(b, tagb, deststnr, d1);
 259                if (flags & DEST_RM)
 260                        return real_2op_NaN(a, taga, deststnr, d2);
 261                else
 262                        return real_2op_NaN(b, tagb, deststnr, d2);
 263        }
 264
 265        return add_sub_specials(a, taga, signa, b, tagb, signb ^ SIGN_NEG,
 266                                dest, deststnr, control_w);
 267}
 268
 269static
 270int add_sub_specials(FPU_REG const *a, u_char taga, u_char signa,
 271                     FPU_REG const *b, u_char tagb, u_char signb,
 272                     FPU_REG * dest, int deststnr, int control_w)
 273{
 274        if (((taga == TW_Denormal) || (tagb == TW_Denormal))
 275            && (denormal_operand() < 0))
 276                return FPU_Exception;
 277
 278        if (taga == TAG_Zero) {
 279                if (tagb == TAG_Zero) {
 280                        /* Both are zero, result will be zero. */
 281                        u_char different_signs = signa ^ signb;
 282
 283                        FPU_copy_to_regi(a, TAG_Zero, deststnr);
 284                        if (different_signs) {
 285                                /* Signs are different. */
 286                                /* Sign of answer depends upon rounding mode. */
 287                                setsign(dest, ((control_w & CW_RC) != RC_DOWN)
 288                                        ? SIGN_POS : SIGN_NEG);
 289                        } else
 290                                setsign(dest, signa);   /* signa may differ from the sign of a. */
 291                        return TAG_Zero;
 292                } else {
 293                        reg_copy(b, dest);
 294                        if ((tagb == TW_Denormal) && (b->sigh & 0x80000000)) {
 295                                /* A pseudoDenormal, convert it. */
 296                                addexponent(dest, 1);
 297                                tagb = TAG_Valid;
 298                        } else if (tagb > TAG_Empty)
 299                                tagb = TAG_Special;
 300                        setsign(dest, signb);   /* signb may differ from the sign of b. */
 301                        FPU_settagi(deststnr, tagb);
 302                        return tagb;
 303                }
 304        } else if (tagb == TAG_Zero) {
 305                reg_copy(a, dest);
 306                if ((taga == TW_Denormal) && (a->sigh & 0x80000000)) {
 307                        /* A pseudoDenormal */
 308                        addexponent(dest, 1);
 309                        taga = TAG_Valid;
 310                } else if (taga > TAG_Empty)
 311                        taga = TAG_Special;
 312                setsign(dest, signa);   /* signa may differ from the sign of a. */
 313                FPU_settagi(deststnr, taga);
 314                return taga;
 315        } else if (taga == TW_Infinity) {
 316                if ((tagb != TW_Infinity) || (signa == signb)) {
 317                        FPU_copy_to_regi(a, TAG_Special, deststnr);
 318                        setsign(dest, signa);   /* signa may differ from the sign of a. */
 319                        return taga;
 320                }
 321                /* Infinity-Infinity is undefined. */
 322                return arith_invalid(deststnr);
 323        } else if (tagb == TW_Infinity) {
 324                FPU_copy_to_regi(b, TAG_Special, deststnr);
 325                setsign(dest, signb);   /* signb may differ from the sign of b. */
 326                return tagb;
 327        }
 328#ifdef PARANOID
 329        EXCEPTION(EX_INTERNAL | 0x101);
 330#endif
 331
 332        return FPU_Exception;
 333}
 334