xref: /openbmc/linux/arch/x86/math-emu/reg_add_sub.c (revision 498495dba268b20e8eadd7fe93c140c68b6cc9d2)
1*b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2da957e11SThomas Gleixner /*---------------------------------------------------------------------------+
3da957e11SThomas Gleixner  |  reg_add_sub.c                                                            |
4da957e11SThomas Gleixner  |                                                                           |
5da957e11SThomas Gleixner  | Functions to add or subtract two registers and put the result in a third. |
6da957e11SThomas Gleixner  |                                                                           |
7da957e11SThomas Gleixner  | Copyright (C) 1992,1993,1997                                              |
8da957e11SThomas Gleixner  |                  W. Metzenthen, 22 Parker St, Ormond, Vic 3163, Australia |
9da957e11SThomas Gleixner  |                  E-mail   billm@suburbia.net                              |
10da957e11SThomas Gleixner  |                                                                           |
11da957e11SThomas Gleixner  |                                                                           |
12da957e11SThomas Gleixner  +---------------------------------------------------------------------------*/
13da957e11SThomas Gleixner 
14da957e11SThomas Gleixner /*---------------------------------------------------------------------------+
15da957e11SThomas Gleixner  |  For each function, the destination may be any FPU_REG, including one of  |
16da957e11SThomas Gleixner  | the source FPU_REGs.                                                      |
17da957e11SThomas Gleixner  |  Each function returns 0 if the answer is o.k., otherwise a non-zero      |
18da957e11SThomas Gleixner  | value is returned, indicating either an exception condition or an         |
19da957e11SThomas Gleixner  | internal error.                                                           |
20da957e11SThomas Gleixner  +---------------------------------------------------------------------------*/
21da957e11SThomas Gleixner 
22da957e11SThomas Gleixner #include "exception.h"
23da957e11SThomas Gleixner #include "reg_constant.h"
24da957e11SThomas Gleixner #include "fpu_emu.h"
25da957e11SThomas Gleixner #include "control_w.h"
26da957e11SThomas Gleixner #include "fpu_system.h"
27da957e11SThomas Gleixner 
28da957e11SThomas Gleixner static
29da957e11SThomas Gleixner int add_sub_specials(FPU_REG const *a, u_char taga, u_char signa,
30da957e11SThomas Gleixner 		     FPU_REG const *b, u_char tagb, u_char signb,
31da957e11SThomas Gleixner 		     FPU_REG * dest, int deststnr, int control_w);
32da957e11SThomas Gleixner 
33da957e11SThomas Gleixner /*
34da957e11SThomas Gleixner   Operates on st(0) and st(n), or on st(0) and temporary data.
35da957e11SThomas Gleixner   The destination must be one of the source st(x).
36da957e11SThomas Gleixner   */
FPU_add(FPU_REG const * b,u_char tagb,int deststnr,int control_w)37da957e11SThomas Gleixner int FPU_add(FPU_REG const *b, u_char tagb, int deststnr, int control_w)
38da957e11SThomas Gleixner {
39da957e11SThomas Gleixner 	FPU_REG *a = &st(0);
40da957e11SThomas Gleixner 	FPU_REG *dest = &st(deststnr);
41da957e11SThomas Gleixner 	u_char signb = getsign(b);
42da957e11SThomas Gleixner 	u_char taga = FPU_gettag0();
43da957e11SThomas Gleixner 	u_char signa = getsign(a);
44da957e11SThomas Gleixner 	u_char saved_sign = getsign(dest);
45da957e11SThomas Gleixner 	int diff, tag, expa, expb;
46da957e11SThomas Gleixner 
473d0d14f9SIngo Molnar 	if (!(taga | tagb)) {
48da957e11SThomas Gleixner 		expa = exponent(a);
49da957e11SThomas Gleixner 		expb = exponent(b);
50da957e11SThomas Gleixner 
51da957e11SThomas Gleixner 	      valid_add:
52da957e11SThomas Gleixner 		/* Both registers are valid */
533d0d14f9SIngo Molnar 		if (!(signa ^ signb)) {
54da957e11SThomas Gleixner 			/* signs are the same */
553d0d14f9SIngo Molnar 			tag =
563d0d14f9SIngo Molnar 			    FPU_u_add(a, b, dest, control_w, signa, expa, expb);
573d0d14f9SIngo Molnar 		} else {
58da957e11SThomas Gleixner 			/* The signs are different, so do a subtraction */
59da957e11SThomas Gleixner 			diff = expa - expb;
603d0d14f9SIngo Molnar 			if (!diff) {
61da957e11SThomas Gleixner 				diff = a->sigh - b->sigh;	/* This works only if the ms bits
62da957e11SThomas Gleixner 								   are identical. */
633d0d14f9SIngo Molnar 				if (!diff) {
64da957e11SThomas Gleixner 					diff = a->sigl > b->sigl;
65da957e11SThomas Gleixner 					if (!diff)
66da957e11SThomas Gleixner 						diff = -(a->sigl < b->sigl);
67da957e11SThomas Gleixner 				}
68da957e11SThomas Gleixner 			}
69da957e11SThomas Gleixner 
703d0d14f9SIngo Molnar 			if (diff > 0) {
713d0d14f9SIngo Molnar 				tag =
723d0d14f9SIngo Molnar 				    FPU_u_sub(a, b, dest, control_w, signa,
733d0d14f9SIngo Molnar 					      expa, expb);
743d0d14f9SIngo Molnar 			} else if (diff < 0) {
753d0d14f9SIngo Molnar 				tag =
763d0d14f9SIngo Molnar 				    FPU_u_sub(b, a, dest, control_w, signb,
773d0d14f9SIngo Molnar 					      expb, expa);
783d0d14f9SIngo Molnar 			} else {
79da957e11SThomas Gleixner 				FPU_copy_to_regi(&CONST_Z, TAG_Zero, deststnr);
80da957e11SThomas Gleixner 				/* sign depends upon rounding mode */
81da957e11SThomas Gleixner 				setsign(dest, ((control_w & CW_RC) != RC_DOWN)
82da957e11SThomas Gleixner 					? SIGN_POS : SIGN_NEG);
83da957e11SThomas Gleixner 				return TAG_Zero;
84da957e11SThomas Gleixner 			}
85da957e11SThomas Gleixner 		}
86da957e11SThomas Gleixner 
873d0d14f9SIngo Molnar 		if (tag < 0) {
88da957e11SThomas Gleixner 			setsign(dest, saved_sign);
89da957e11SThomas Gleixner 			return tag;
90da957e11SThomas Gleixner 		}
91da957e11SThomas Gleixner 		FPU_settagi(deststnr, tag);
92da957e11SThomas Gleixner 		return tag;
93da957e11SThomas Gleixner 	}
94da957e11SThomas Gleixner 
95da957e11SThomas Gleixner 	if (taga == TAG_Special)
96da957e11SThomas Gleixner 		taga = FPU_Special(a);
97da957e11SThomas Gleixner 	if (tagb == TAG_Special)
98da957e11SThomas Gleixner 		tagb = FPU_Special(b);
99da957e11SThomas Gleixner 
100da957e11SThomas Gleixner 	if (((taga == TAG_Valid) && (tagb == TW_Denormal))
101da957e11SThomas Gleixner 	    || ((taga == TW_Denormal) && (tagb == TAG_Valid))
1023d0d14f9SIngo Molnar 	    || ((taga == TW_Denormal) && (tagb == TW_Denormal))) {
103da957e11SThomas Gleixner 		FPU_REG x, y;
104da957e11SThomas Gleixner 
105da957e11SThomas Gleixner 		if (denormal_operand() < 0)
106da957e11SThomas Gleixner 			return FPU_Exception;
107da957e11SThomas Gleixner 
108da957e11SThomas Gleixner 		FPU_to_exp16(a, &x);
109da957e11SThomas Gleixner 		FPU_to_exp16(b, &y);
110da957e11SThomas Gleixner 		a = &x;
111da957e11SThomas Gleixner 		b = &y;
112da957e11SThomas Gleixner 		expa = exponent16(a);
113da957e11SThomas Gleixner 		expb = exponent16(b);
114da957e11SThomas Gleixner 		goto valid_add;
115da957e11SThomas Gleixner 	}
116da957e11SThomas Gleixner 
1173d0d14f9SIngo Molnar 	if ((taga == TW_NaN) || (tagb == TW_NaN)) {
118da957e11SThomas Gleixner 		if (deststnr == 0)
119da957e11SThomas Gleixner 			return real_2op_NaN(b, tagb, deststnr, a);
120da957e11SThomas Gleixner 		else
121da957e11SThomas Gleixner 			return real_2op_NaN(a, taga, deststnr, a);
122da957e11SThomas Gleixner 	}
123da957e11SThomas Gleixner 
124da957e11SThomas Gleixner 	return add_sub_specials(a, taga, signa, b, tagb, signb,
125da957e11SThomas Gleixner 				dest, deststnr, control_w);
126da957e11SThomas Gleixner }
127da957e11SThomas Gleixner 
128da957e11SThomas Gleixner /* Subtract b from a.  (a-b) -> dest */
FPU_sub(int flags,int rm,int control_w)129da957e11SThomas Gleixner int FPU_sub(int flags, int rm, int control_w)
130da957e11SThomas Gleixner {
131da957e11SThomas Gleixner 	FPU_REG const *a, *b;
132da957e11SThomas Gleixner 	FPU_REG *dest;
133da957e11SThomas Gleixner 	u_char taga, tagb, signa, signb, saved_sign, sign;
134da957e11SThomas Gleixner 	int diff, tag = 0, expa, expb, deststnr;
135da957e11SThomas Gleixner 
136da957e11SThomas Gleixner 	a = &st(0);
137da957e11SThomas Gleixner 	taga = FPU_gettag0();
138da957e11SThomas Gleixner 
139da957e11SThomas Gleixner 	deststnr = 0;
1403d0d14f9SIngo Molnar 	if (flags & LOADED) {
141da957e11SThomas Gleixner 		b = (FPU_REG *) rm;
142da957e11SThomas Gleixner 		tagb = flags & 0x0f;
1433d0d14f9SIngo Molnar 	} else {
144da957e11SThomas Gleixner 		b = &st(rm);
145da957e11SThomas Gleixner 		tagb = FPU_gettagi(rm);
146da957e11SThomas Gleixner 
147da957e11SThomas Gleixner 		if (flags & DEST_RM)
148da957e11SThomas Gleixner 			deststnr = rm;
149da957e11SThomas Gleixner 	}
150da957e11SThomas Gleixner 
151da957e11SThomas Gleixner 	signa = getsign(a);
152da957e11SThomas Gleixner 	signb = getsign(b);
153da957e11SThomas Gleixner 
1543d0d14f9SIngo Molnar 	if (flags & REV) {
155da957e11SThomas Gleixner 		signa ^= SIGN_NEG;
156da957e11SThomas Gleixner 		signb ^= SIGN_NEG;
157da957e11SThomas Gleixner 	}
158da957e11SThomas Gleixner 
159da957e11SThomas Gleixner 	dest = &st(deststnr);
160da957e11SThomas Gleixner 	saved_sign = getsign(dest);
161da957e11SThomas Gleixner 
1623d0d14f9SIngo Molnar 	if (!(taga | tagb)) {
163da957e11SThomas Gleixner 		expa = exponent(a);
164da957e11SThomas Gleixner 		expb = exponent(b);
165da957e11SThomas Gleixner 
166da957e11SThomas Gleixner 	      valid_subtract:
167da957e11SThomas Gleixner 		/* Both registers are valid */
168da957e11SThomas Gleixner 
169da957e11SThomas Gleixner 		diff = expa - expb;
170da957e11SThomas Gleixner 
1713d0d14f9SIngo Molnar 		if (!diff) {
172da957e11SThomas Gleixner 			diff = a->sigh - b->sigh;	/* Works only if ms bits are identical */
1733d0d14f9SIngo Molnar 			if (!diff) {
174da957e11SThomas Gleixner 				diff = a->sigl > b->sigl;
175da957e11SThomas Gleixner 				if (!diff)
176da957e11SThomas Gleixner 					diff = -(a->sigl < b->sigl);
177da957e11SThomas Gleixner 			}
178da957e11SThomas Gleixner 		}
179da957e11SThomas Gleixner 
1803d0d14f9SIngo Molnar 		switch ((((int)signa) * 2 + signb) / SIGN_NEG) {
181da957e11SThomas Gleixner 		case 0:	/* P - P */
182da957e11SThomas Gleixner 		case 3:	/* N - N */
1833d0d14f9SIngo Molnar 			if (diff > 0) {
184da957e11SThomas Gleixner 				/* |a| > |b| */
1853d0d14f9SIngo Molnar 				tag =
1863d0d14f9SIngo Molnar 				    FPU_u_sub(a, b, dest, control_w, signa,
1873d0d14f9SIngo Molnar 					      expa, expb);
1883d0d14f9SIngo Molnar 			} else if (diff == 0) {
189da957e11SThomas Gleixner 				FPU_copy_to_regi(&CONST_Z, TAG_Zero, deststnr);
190da957e11SThomas Gleixner 
191da957e11SThomas Gleixner 				/* sign depends upon rounding mode */
192da957e11SThomas Gleixner 				setsign(dest, ((control_w & CW_RC) != RC_DOWN)
193da957e11SThomas Gleixner 					? SIGN_POS : SIGN_NEG);
194da957e11SThomas Gleixner 				return TAG_Zero;
1953d0d14f9SIngo Molnar 			} else {
196da957e11SThomas Gleixner 				sign = signa ^ SIGN_NEG;
1973d0d14f9SIngo Molnar 				tag =
1983d0d14f9SIngo Molnar 				    FPU_u_sub(b, a, dest, control_w, sign, expb,
1993d0d14f9SIngo Molnar 					      expa);
200da957e11SThomas Gleixner 			}
201da957e11SThomas Gleixner 			break;
202da957e11SThomas Gleixner 		case 1:	/* P - N */
2033d0d14f9SIngo Molnar 			tag =
2043d0d14f9SIngo Molnar 			    FPU_u_add(a, b, dest, control_w, SIGN_POS, expa,
2053d0d14f9SIngo Molnar 				      expb);
206da957e11SThomas Gleixner 			break;
207da957e11SThomas Gleixner 		case 2:	/* N - P */
2083d0d14f9SIngo Molnar 			tag =
2093d0d14f9SIngo Molnar 			    FPU_u_add(a, b, dest, control_w, SIGN_NEG, expa,
2103d0d14f9SIngo Molnar 				      expb);
211da957e11SThomas Gleixner 			break;
212da957e11SThomas Gleixner #ifdef PARANOID
213da957e11SThomas Gleixner 		default:
214da957e11SThomas Gleixner 			EXCEPTION(EX_INTERNAL | 0x111);
215da957e11SThomas Gleixner 			return -1;
216da957e11SThomas Gleixner #endif
217da957e11SThomas Gleixner 		}
2183d0d14f9SIngo Molnar 		if (tag < 0) {
219da957e11SThomas Gleixner 			setsign(dest, saved_sign);
220da957e11SThomas Gleixner 			return tag;
221da957e11SThomas Gleixner 		}
222da957e11SThomas Gleixner 		FPU_settagi(deststnr, tag);
223da957e11SThomas Gleixner 		return tag;
224da957e11SThomas Gleixner 	}
225da957e11SThomas Gleixner 
226da957e11SThomas Gleixner 	if (taga == TAG_Special)
227da957e11SThomas Gleixner 		taga = FPU_Special(a);
228da957e11SThomas Gleixner 	if (tagb == TAG_Special)
229da957e11SThomas Gleixner 		tagb = FPU_Special(b);
230da957e11SThomas Gleixner 
231da957e11SThomas Gleixner 	if (((taga == TAG_Valid) && (tagb == TW_Denormal))
232da957e11SThomas Gleixner 	    || ((taga == TW_Denormal) && (tagb == TAG_Valid))
2333d0d14f9SIngo Molnar 	    || ((taga == TW_Denormal) && (tagb == TW_Denormal))) {
234da957e11SThomas Gleixner 		FPU_REG x, y;
235da957e11SThomas Gleixner 
236da957e11SThomas Gleixner 		if (denormal_operand() < 0)
237da957e11SThomas Gleixner 			return FPU_Exception;
238da957e11SThomas Gleixner 
239da957e11SThomas Gleixner 		FPU_to_exp16(a, &x);
240da957e11SThomas Gleixner 		FPU_to_exp16(b, &y);
241da957e11SThomas Gleixner 		a = &x;
242da957e11SThomas Gleixner 		b = &y;
243da957e11SThomas Gleixner 		expa = exponent16(a);
244da957e11SThomas Gleixner 		expb = exponent16(b);
245da957e11SThomas Gleixner 
246da957e11SThomas Gleixner 		goto valid_subtract;
247da957e11SThomas Gleixner 	}
248da957e11SThomas Gleixner 
2493d0d14f9SIngo Molnar 	if ((taga == TW_NaN) || (tagb == TW_NaN)) {
250da957e11SThomas Gleixner 		FPU_REG const *d1, *d2;
2513d0d14f9SIngo Molnar 		if (flags & REV) {
252da957e11SThomas Gleixner 			d1 = b;
253da957e11SThomas Gleixner 			d2 = a;
2543d0d14f9SIngo Molnar 		} else {
255da957e11SThomas Gleixner 			d1 = a;
256da957e11SThomas Gleixner 			d2 = b;
257da957e11SThomas Gleixner 		}
258da957e11SThomas Gleixner 		if (flags & LOADED)
259da957e11SThomas Gleixner 			return real_2op_NaN(b, tagb, deststnr, d1);
260da957e11SThomas Gleixner 		if (flags & DEST_RM)
261da957e11SThomas Gleixner 			return real_2op_NaN(a, taga, deststnr, d2);
262da957e11SThomas Gleixner 		else
263da957e11SThomas Gleixner 			return real_2op_NaN(b, tagb, deststnr, d2);
264da957e11SThomas Gleixner 	}
265da957e11SThomas Gleixner 
266da957e11SThomas Gleixner 	return add_sub_specials(a, taga, signa, b, tagb, signb ^ SIGN_NEG,
267da957e11SThomas Gleixner 				dest, deststnr, control_w);
268da957e11SThomas Gleixner }
269da957e11SThomas Gleixner 
270da957e11SThomas Gleixner static
add_sub_specials(FPU_REG const * a,u_char taga,u_char signa,FPU_REG const * b,u_char tagb,u_char signb,FPU_REG * dest,int deststnr,int control_w)271da957e11SThomas Gleixner int add_sub_specials(FPU_REG const *a, u_char taga, u_char signa,
272da957e11SThomas Gleixner 		     FPU_REG const *b, u_char tagb, u_char signb,
273da957e11SThomas Gleixner 		     FPU_REG * dest, int deststnr, int control_w)
274da957e11SThomas Gleixner {
275da957e11SThomas Gleixner 	if (((taga == TW_Denormal) || (tagb == TW_Denormal))
276da957e11SThomas Gleixner 	    && (denormal_operand() < 0))
277da957e11SThomas Gleixner 		return FPU_Exception;
278da957e11SThomas Gleixner 
2793d0d14f9SIngo Molnar 	if (taga == TAG_Zero) {
2803d0d14f9SIngo Molnar 		if (tagb == TAG_Zero) {
281da957e11SThomas Gleixner 			/* Both are zero, result will be zero. */
282da957e11SThomas Gleixner 			u_char different_signs = signa ^ signb;
283da957e11SThomas Gleixner 
284da957e11SThomas Gleixner 			FPU_copy_to_regi(a, TAG_Zero, deststnr);
2853d0d14f9SIngo Molnar 			if (different_signs) {
286da957e11SThomas Gleixner 				/* Signs are different. */
287da957e11SThomas Gleixner 				/* Sign of answer depends upon rounding mode. */
288da957e11SThomas Gleixner 				setsign(dest, ((control_w & CW_RC) != RC_DOWN)
289da957e11SThomas Gleixner 					? SIGN_POS : SIGN_NEG);
2903d0d14f9SIngo Molnar 			} else
291da957e11SThomas Gleixner 				setsign(dest, signa);	/* signa may differ from the sign of a. */
292da957e11SThomas Gleixner 			return TAG_Zero;
2933d0d14f9SIngo Molnar 		} else {
294da957e11SThomas Gleixner 			reg_copy(b, dest);
2953d0d14f9SIngo Molnar 			if ((tagb == TW_Denormal) && (b->sigh & 0x80000000)) {
296da957e11SThomas Gleixner 				/* A pseudoDenormal, convert it. */
297da957e11SThomas Gleixner 				addexponent(dest, 1);
298da957e11SThomas Gleixner 				tagb = TAG_Valid;
2993d0d14f9SIngo Molnar 			} else if (tagb > TAG_Empty)
300da957e11SThomas Gleixner 				tagb = TAG_Special;
301da957e11SThomas Gleixner 			setsign(dest, signb);	/* signb may differ from the sign of b. */
302da957e11SThomas Gleixner 			FPU_settagi(deststnr, tagb);
303da957e11SThomas Gleixner 			return tagb;
304da957e11SThomas Gleixner 		}
3053d0d14f9SIngo Molnar 	} else if (tagb == TAG_Zero) {
306da957e11SThomas Gleixner 		reg_copy(a, dest);
3073d0d14f9SIngo Molnar 		if ((taga == TW_Denormal) && (a->sigh & 0x80000000)) {
308da957e11SThomas Gleixner 			/* A pseudoDenormal */
309da957e11SThomas Gleixner 			addexponent(dest, 1);
310da957e11SThomas Gleixner 			taga = TAG_Valid;
3113d0d14f9SIngo Molnar 		} else if (taga > TAG_Empty)
312da957e11SThomas Gleixner 			taga = TAG_Special;
313da957e11SThomas Gleixner 		setsign(dest, signa);	/* signa may differ from the sign of a. */
314da957e11SThomas Gleixner 		FPU_settagi(deststnr, taga);
315da957e11SThomas Gleixner 		return taga;
3163d0d14f9SIngo Molnar 	} else if (taga == TW_Infinity) {
3173d0d14f9SIngo Molnar 		if ((tagb != TW_Infinity) || (signa == signb)) {
318da957e11SThomas Gleixner 			FPU_copy_to_regi(a, TAG_Special, deststnr);
319da957e11SThomas Gleixner 			setsign(dest, signa);	/* signa may differ from the sign of a. */
320da957e11SThomas Gleixner 			return taga;
321da957e11SThomas Gleixner 		}
322da957e11SThomas Gleixner 		/* Infinity-Infinity is undefined. */
323da957e11SThomas Gleixner 		return arith_invalid(deststnr);
3243d0d14f9SIngo Molnar 	} else if (tagb == TW_Infinity) {
325da957e11SThomas Gleixner 		FPU_copy_to_regi(b, TAG_Special, deststnr);
326da957e11SThomas Gleixner 		setsign(dest, signb);	/* signb may differ from the sign of b. */
327da957e11SThomas Gleixner 		return tagb;
328da957e11SThomas Gleixner 	}
329da957e11SThomas Gleixner #ifdef PARANOID
330da957e11SThomas Gleixner 	EXCEPTION(EX_INTERNAL | 0x101);
331da957e11SThomas Gleixner #endif
332da957e11SThomas Gleixner 
333da957e11SThomas Gleixner 	return FPU_Exception;
334da957e11SThomas Gleixner }
335