blob: deea48b9f13a41c6807ecde0ca0f7f9d4d39bd60 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*---------------------------------------------------------------------------+
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,
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010030 FPU_REG * dest, int deststnr, int control_w);
Linus Torvalds1da177e2005-04-16 15:20:36 -070031
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{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010038 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;
Linus Torvalds1da177e2005-04-16 15:20:36 -070045
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010046 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 }
Linus Torvalds1da177e2005-04-16 15:20:36 -070084 }
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010085
86 if (tag < 0) {
87 setsign(dest, saved_sign);
88 return tag;
89 }
90 FPU_settagi(deststnr, tag);
91 return tag;
Linus Torvalds1da177e2005-04-16 15:20:36 -070092 }
93
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010094 if (taga == TAG_Special)
95 taga = FPU_Special(a);
96 if (tagb == TAG_Special)
97 tagb = FPU_Special(b);
Linus Torvalds1da177e2005-04-16 15:20:36 -070098
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010099 if (((taga == TAG_Valid) && (tagb == TW_Denormal))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700100 || ((taga == TW_Denormal) && (tagb == TAG_Valid))
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100101 || ((taga == TW_Denormal) && (tagb == TW_Denormal))) {
102 FPU_REG x, y;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700103
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100104 if (denormal_operand() < 0)
105 return FPU_Exception;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700106
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100107 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 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700115
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100116 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 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700122
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100123 return add_sub_specials(a, taga, signa, b, tagb, signb,
124 dest, deststnr, control_w);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700125}
126
Linus Torvalds1da177e2005-04-16 15:20:36 -0700127/* Subtract b from a. (a-b) -> dest */
128int FPU_sub(int flags, int rm, int control_w)
129{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100130 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;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700134
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100135 a = &st(0);
136 taga = FPU_gettag0();
Linus Torvalds1da177e2005-04-16 15:20:36 -0700137
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100138 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);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700145
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100146 if (flags & DEST_RM)
147 deststnr = rm;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700148 }
149
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100150 signa = getsign(a);
151 signb = getsign(b);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700152
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100153 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;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700211#ifdef PARANOID
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100212 default:
213 EXCEPTION(EX_INTERNAL | 0x111);
214 return -1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700215#endif
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100216 }
217 if (tag < 0) {
218 setsign(dest, saved_sign);
219 return tag;
220 }
221 FPU_settagi(deststnr, tag);
222 return tag;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700223 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700224
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100225 if (taga == TAG_Special)
226 taga = FPU_Special(a);
227 if (tagb == TAG_Special)
228 tagb = FPU_Special(b);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700229
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100230 if (((taga == TAG_Valid) && (tagb == TW_Denormal))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700231 || ((taga == TW_Denormal) && (tagb == TAG_Valid))
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100232 || ((taga == TW_Denormal) && (tagb == TW_Denormal))) {
233 FPU_REG x, y;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700234
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100235 if (denormal_operand() < 0)
236 return FPU_Exception;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700237
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100238 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);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700244
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100245 goto valid_subtract;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700246 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700247
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100248 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);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700267}
268
Linus Torvalds1da177e2005-04-16 15:20:36 -0700269static
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,
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100272 FPU_REG * dest, int deststnr, int control_w)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700273{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100274 if (((taga == TW_Denormal) || (tagb == TW_Denormal))
275 && (denormal_operand() < 0))
276 return FPU_Exception;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700277
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100278 if (taga == TAG_Zero) {
279 if (tagb == TAG_Zero) {
280 /* Both are zero, result will be zero. */
281 u_char different_signs = signa ^ signb;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700282
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100283 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;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700327 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700328#ifdef PARANOID
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100329 EXCEPTION(EX_INTERNAL | 0x101);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700330#endif
331
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100332 return FPU_Exception;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700333}