Linux-2.6.12-rc2
[safe/jmp/linux-2.6] / arch / i386 / math-emu / reg_add_sub.c
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
27 static
28 int 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   */
36 int 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     {
48       expa = exponent(a);
49       expb = exponent(b);
50
51     valid_add:
52       /* Both registers are valid */
53       if (!(signa ^ signb))
54         {
55           /* signs are the same */
56           tag = FPU_u_add(a, b, dest, control_w, signa, expa, expb);
57         }
58       else
59         {
60           /* The signs are different, so do a subtraction */
61           diff = expa - expb;
62           if (!diff)
63             {
64               diff = a->sigh - b->sigh;  /* This works only if the ms bits
65                                             are identical. */
66               if (!diff)
67                 {
68                   diff = a->sigl > b->sigl;
69                   if (!diff)
70                     diff = -(a->sigl < b->sigl);
71                 }
72             }
73       
74           if (diff > 0)
75             {
76               tag = FPU_u_sub(a, b, dest, control_w, signa, expa, expb);
77             }
78           else if ( diff < 0 )
79             {
80               tag = FPU_u_sub(b, a, dest, control_w, signb, expb, expa);
81             }
82           else
83             {
84               FPU_copy_to_regi(&CONST_Z, TAG_Zero, deststnr);
85               /* sign depends upon rounding mode */
86               setsign(dest, ((control_w & CW_RC) != RC_DOWN)
87                       ? SIGN_POS : SIGN_NEG);
88               return TAG_Zero;
89             }
90         }
91
92       if ( tag < 0 )
93         {
94           setsign(dest, saved_sign);
95           return tag;
96         }
97       FPU_settagi(deststnr, tag);
98       return tag;
99     }
100
101   if ( taga == TAG_Special )
102     taga = FPU_Special(a);
103   if ( tagb == TAG_Special )
104     tagb = FPU_Special(b);
105
106   if ( ((taga == TAG_Valid) && (tagb == TW_Denormal))
107             || ((taga == TW_Denormal) && (tagb == TAG_Valid))
108             || ((taga == TW_Denormal) && (tagb == TW_Denormal)) )
109     {
110       FPU_REG x, y;
111
112       if ( denormal_operand() < 0 )
113         return FPU_Exception;
114
115       FPU_to_exp16(a, &x);
116       FPU_to_exp16(b, &y);
117       a = &x;
118       b = &y;
119       expa = exponent16(a);
120       expb = exponent16(b);
121       goto valid_add;
122     }
123
124   if ( (taga == TW_NaN) || (tagb == TW_NaN) )
125     {
126       if ( deststnr == 0 )
127         return real_2op_NaN(b, tagb, deststnr, a);
128       else
129         return real_2op_NaN(a, taga, deststnr, a);
130     }
131
132   return add_sub_specials(a, taga, signa, b, tagb, signb,
133                           dest, deststnr, control_w);
134 }
135
136
137 /* Subtract b from a.  (a-b) -> dest */
138 int FPU_sub(int flags, int rm, int control_w)
139 {
140   FPU_REG const *a, *b;
141   FPU_REG *dest;
142   u_char taga, tagb, signa, signb, saved_sign, sign;
143   int diff, tag = 0, expa, expb, deststnr;
144
145   a = &st(0);
146   taga = FPU_gettag0();
147
148   deststnr = 0;
149   if ( flags & LOADED )
150     {
151       b = (FPU_REG *)rm;
152       tagb = flags & 0x0f;
153     }
154   else
155     {
156       b = &st(rm);
157       tagb = FPU_gettagi(rm);
158
159       if ( flags & DEST_RM )
160         deststnr = rm;
161     }
162
163   signa = getsign(a);
164   signb = getsign(b);
165
166   if ( flags & REV )
167     {
168       signa ^= SIGN_NEG;
169       signb ^= SIGN_NEG;
170     }
171
172   dest = &st(deststnr);
173   saved_sign = getsign(dest);
174
175   if ( !(taga | tagb) )
176     {
177       expa = exponent(a);
178       expb = exponent(b);
179
180     valid_subtract:
181       /* Both registers are valid */
182
183       diff = expa - expb;
184
185       if (!diff)
186         {
187           diff = a->sigh - b->sigh;  /* Works only if ms bits are identical */
188           if (!diff)
189             {
190               diff = a->sigl > b->sigl;
191               if (!diff)
192                 diff = -(a->sigl < b->sigl);
193             }
194         }
195
196       switch ( (((int)signa)*2 + signb) / SIGN_NEG )
197         {
198         case 0: /* P - P */
199         case 3: /* N - N */
200           if (diff > 0)
201             {
202               /* |a| > |b| */
203               tag = FPU_u_sub(a, b, dest, control_w, signa, expa, expb);
204             }
205           else if ( diff == 0 )
206             {
207               FPU_copy_to_regi(&CONST_Z, TAG_Zero, deststnr);
208
209               /* sign depends upon rounding mode */
210               setsign(dest, ((control_w & CW_RC) != RC_DOWN)
211                 ? SIGN_POS : SIGN_NEG);
212               return TAG_Zero;
213             }
214           else
215             {
216               sign = signa ^ SIGN_NEG;
217               tag = FPU_u_sub(b, a, dest, control_w, sign, expb, expa);
218             }
219           break;
220         case 1: /* P - N */
221           tag = FPU_u_add(a, b, dest, control_w, SIGN_POS, expa, expb);
222           break;
223         case 2: /* N - P */
224           tag = FPU_u_add(a, b, dest, control_w, SIGN_NEG, expa, expb);
225           break;
226 #ifdef PARANOID
227         default:
228           EXCEPTION(EX_INTERNAL|0x111);
229           return -1;
230 #endif
231         }
232       if ( tag < 0 )
233         {
234           setsign(dest, saved_sign);
235           return tag;
236         }
237       FPU_settagi(deststnr, tag);
238       return tag;
239     }
240
241   if ( taga == TAG_Special )
242     taga = FPU_Special(a);
243   if ( tagb == TAG_Special )
244     tagb = FPU_Special(b);
245
246   if ( ((taga == TAG_Valid) && (tagb == TW_Denormal))
247             || ((taga == TW_Denormal) && (tagb == TAG_Valid))
248             || ((taga == TW_Denormal) && (tagb == TW_Denormal)) )
249     {
250       FPU_REG x, y;
251
252       if ( denormal_operand() < 0 )
253         return FPU_Exception;
254
255       FPU_to_exp16(a, &x);
256       FPU_to_exp16(b, &y);
257       a = &x;
258       b = &y;
259       expa = exponent16(a);
260       expb = exponent16(b);
261
262       goto valid_subtract;
263     }
264
265   if ( (taga == TW_NaN) || (tagb == TW_NaN) )
266     {
267       FPU_REG const *d1, *d2;
268       if ( flags & REV )
269         {
270           d1 = b;
271           d2 = a;
272         }
273       else
274         {
275           d1 = a;
276           d2 = b;
277         }
278       if ( flags & LOADED )
279         return real_2op_NaN(b, tagb, deststnr, d1);
280       if ( flags & DEST_RM )
281         return real_2op_NaN(a, taga, deststnr, d2);
282       else
283         return real_2op_NaN(b, tagb, deststnr, d2);
284     }
285
286     return add_sub_specials(a, taga, signa, b, tagb, signb ^ SIGN_NEG,
287                             dest, deststnr, control_w);
288 }
289
290
291 static
292 int add_sub_specials(FPU_REG const *a, u_char taga, u_char signa,
293                      FPU_REG const *b, u_char tagb, u_char signb,
294                      FPU_REG *dest, int deststnr, int control_w)
295 {
296   if ( ((taga == TW_Denormal) || (tagb == TW_Denormal))
297        && (denormal_operand() < 0) )
298     return FPU_Exception;
299
300   if (taga == TAG_Zero)
301     {
302       if (tagb == TAG_Zero)
303         {
304           /* Both are zero, result will be zero. */
305           u_char different_signs = signa ^ signb;
306
307           FPU_copy_to_regi(a, TAG_Zero, deststnr);
308           if ( different_signs )
309             {
310               /* Signs are different. */
311               /* Sign of answer depends upon rounding mode. */
312               setsign(dest, ((control_w & CW_RC) != RC_DOWN)
313                       ? SIGN_POS : SIGN_NEG);
314             }
315           else
316             setsign(dest, signa);  /* signa may differ from the sign of a. */
317           return TAG_Zero;
318         }
319       else
320         {
321           reg_copy(b, dest);
322           if ( (tagb == TW_Denormal) && (b->sigh & 0x80000000) )
323             {
324               /* A pseudoDenormal, convert it. */
325               addexponent(dest, 1);
326               tagb = TAG_Valid;
327             }
328           else if ( tagb > TAG_Empty )
329             tagb = TAG_Special;
330           setsign(dest, signb);  /* signb may differ from the sign of b. */
331           FPU_settagi(deststnr, tagb);
332           return tagb;
333         }
334     }
335   else if (tagb == TAG_Zero)
336     {
337       reg_copy(a, dest);
338       if ( (taga == TW_Denormal) && (a->sigh & 0x80000000) )
339         {
340           /* A pseudoDenormal */
341           addexponent(dest, 1);
342           taga = TAG_Valid;
343         }
344       else if ( taga > TAG_Empty )
345         taga = TAG_Special;
346       setsign(dest, signa);  /* signa may differ from the sign of a. */
347       FPU_settagi(deststnr, taga);
348       return taga;
349     }
350   else if (taga == TW_Infinity)
351     {
352       if ( (tagb != TW_Infinity) || (signa == signb) )
353         {
354           FPU_copy_to_regi(a, TAG_Special, deststnr);
355           setsign(dest, signa);  /* signa may differ from the sign of a. */
356           return taga;
357         }
358       /* Infinity-Infinity is undefined. */
359       return arith_invalid(deststnr);
360     }
361   else if (tagb == TW_Infinity)
362     {
363       FPU_copy_to_regi(b, TAG_Special, deststnr);
364       setsign(dest, signb);  /* signb may differ from the sign of b. */
365       return tagb;
366     }
367
368 #ifdef PARANOID
369   EXCEPTION(EX_INTERNAL|0x101);
370 #endif
371
372   return FPU_Exception;
373 }
374