File: /usr/src/linux/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, 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     
375