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    {
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 */
138int 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
291static
292int 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