xref: /openbmc/linux/arch/x86/math-emu/reg_add_sub.c (revision 4f6cce39)
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 		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 */
128 int 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 
269 static
270 int 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