1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /*---------------------------------------------------------------------------+ |
3 | | reg_add_sub.c | |
4 | | | |
5 | | Functions to add or subtract two registers and put the result in a third. | |
6 | | | |
7 | | Copyright (C) 1992,1993,1997 | |
8 | | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, Australia | |
9 | | E-mail billm@suburbia.net | |
10 | | | |
11 | | | |
12 | +---------------------------------------------------------------------------*/ |
13 | |
14 | /*---------------------------------------------------------------------------+ |
15 | | For each function, the destination may be any FPU_REG, including one of | |
16 | | the source FPU_REGs. | |
17 | | Each function returns 0 if the answer is o.k., otherwise a non-zero | |
18 | | value is returned, indicating either an exception condition or an | |
19 | | internal error. | |
20 | +---------------------------------------------------------------------------*/ |
21 | |
22 | #include "exception.h" |
23 | #include "reg_constant.h" |
24 | #include "fpu_emu.h" |
25 | #include "control_w.h" |
26 | #include "fpu_system.h" |
27 | |
28 | static |
29 | int add_sub_specials(FPU_REG const *a, u_char taga, u_char signa, |
30 | FPU_REG const *b, u_char tagb, u_char signb, |
31 | FPU_REG * dest, int deststnr, int control_w); |
32 | |
33 | /* |
34 | Operates on st(0) and st(n), or on st(0) and temporary data. |
35 | The destination must be one of the source st(x). |
36 | */ |
37 | int FPU_add(FPU_REG const *b, u_char tagb, int deststnr, int control_w) |
38 | { |
39 | FPU_REG *a = &st(0); |
40 | FPU_REG *dest = &st(deststnr); |
41 | u_char signb = getsign(b); |
42 | u_char taga = FPU_gettag0(); |
43 | u_char signa = getsign(a); |
44 | u_char saved_sign = getsign(dest); |
45 | int diff, tag, expa, expb; |
46 | |
47 | if (!(taga | tagb)) { |
48 | expa = exponent(a); |
49 | expb = exponent(b); |
50 | |
51 | valid_add: |
52 | /* Both registers are valid */ |
53 | if (!(signa ^ signb)) { |
54 | /* signs are the same */ |
55 | tag = |
56 | FPU_u_add(arg1: a, arg2: b, answ: dest, control_w, sign: signa, expa, expb); |
57 | } else { |
58 | /* The signs are different, so do a subtraction */ |
59 | diff = expa - expb; |
60 | if (!diff) { |
61 | diff = a->sigh - b->sigh; /* This works only if the ms bits |
62 | are identical. */ |
63 | if (!diff) { |
64 | diff = a->sigl > b->sigl; |
65 | if (!diff) |
66 | diff = -(a->sigl < b->sigl); |
67 | } |
68 | } |
69 | |
70 | if (diff > 0) { |
71 | tag = |
72 | FPU_u_sub(arg1: a, arg2: b, answ: dest, control_w, sign: signa, |
73 | expa, expb); |
74 | } else if (diff < 0) { |
75 | tag = |
76 | FPU_u_sub(arg1: b, arg2: a, answ: dest, control_w, sign: signb, |
77 | expa: expb, expb: expa); |
78 | } else { |
79 | FPU_copy_to_regi(r: &CONST_Z, TAG_Zero, stnr: deststnr); |
80 | /* sign depends upon rounding mode */ |
81 | setsign(dest, ((control_w & CW_RC) != RC_DOWN) |
82 | ? SIGN_POS : SIGN_NEG); |
83 | return TAG_Zero; |
84 | } |
85 | } |
86 | |
87 | if (tag < 0) { |
88 | setsign(dest, saved_sign); |
89 | return tag; |
90 | } |
91 | FPU_settagi(stnr: deststnr, tag); |
92 | return tag; |
93 | } |
94 | |
95 | if (taga == TAG_Special) |
96 | taga = FPU_Special(ptr: a); |
97 | if (tagb == TAG_Special) |
98 | tagb = FPU_Special(ptr: b); |
99 | |
100 | if (((taga == TAG_Valid) && (tagb == TW_Denormal)) |
101 | || ((taga == TW_Denormal) && (tagb == TAG_Valid)) |
102 | || ((taga == TW_Denormal) && (tagb == TW_Denormal))) { |
103 | FPU_REG x, y; |
104 | |
105 | if (denormal_operand() < 0) |
106 | return FPU_Exception; |
107 | |
108 | FPU_to_exp16(a, x: &x); |
109 | FPU_to_exp16(a: b, x: &y); |
110 | a = &x; |
111 | b = &y; |
112 | expa = exponent16(a); |
113 | expb = exponent16(b); |
114 | goto valid_add; |
115 | } |
116 | |
117 | if ((taga == TW_NaN) || (tagb == TW_NaN)) { |
118 | if (deststnr == 0) |
119 | return real_2op_NaN(b, tagb, deststnr, defaultNaN: a); |
120 | else |
121 | return real_2op_NaN(b: a, tagb: taga, deststnr, defaultNaN: a); |
122 | } |
123 | |
124 | return add_sub_specials(a, taga, signa, b, tagb, signb, |
125 | dest, deststnr, control_w); |
126 | } |
127 | |
128 | /* Subtract b from a. (a-b) -> dest */ |
129 | int FPU_sub(int flags, int rm, int control_w) |
130 | { |
131 | FPU_REG const *a, *b; |
132 | FPU_REG *dest; |
133 | u_char taga, tagb, signa, signb, saved_sign, sign; |
134 | int diff, tag = 0, expa, expb, deststnr; |
135 | |
136 | a = &st(0); |
137 | taga = FPU_gettag0(); |
138 | |
139 | deststnr = 0; |
140 | if (flags & LOADED) { |
141 | b = (FPU_REG *) rm; |
142 | tagb = flags & 0x0f; |
143 | } else { |
144 | b = &st(rm); |
145 | tagb = FPU_gettagi(stnr: rm); |
146 | |
147 | if (flags & DEST_RM) |
148 | deststnr = rm; |
149 | } |
150 | |
151 | signa = getsign(a); |
152 | signb = getsign(b); |
153 | |
154 | if (flags & REV) { |
155 | signa ^= SIGN_NEG; |
156 | signb ^= SIGN_NEG; |
157 | } |
158 | |
159 | dest = &st(deststnr); |
160 | saved_sign = getsign(dest); |
161 | |
162 | if (!(taga | tagb)) { |
163 | expa = exponent(a); |
164 | expb = exponent(b); |
165 | |
166 | valid_subtract: |
167 | /* Both registers are valid */ |
168 | |
169 | diff = expa - expb; |
170 | |
171 | if (!diff) { |
172 | diff = a->sigh - b->sigh; /* Works only if ms bits are identical */ |
173 | if (!diff) { |
174 | diff = a->sigl > b->sigl; |
175 | if (!diff) |
176 | diff = -(a->sigl < b->sigl); |
177 | } |
178 | } |
179 | |
180 | switch ((((int)signa) * 2 + signb) / SIGN_NEG) { |
181 | case 0: /* P - P */ |
182 | case 3: /* N - N */ |
183 | if (diff > 0) { |
184 | /* |a| > |b| */ |
185 | tag = |
186 | FPU_u_sub(arg1: a, arg2: b, answ: dest, control_w, sign: signa, |
187 | expa, expb); |
188 | } else if (diff == 0) { |
189 | FPU_copy_to_regi(r: &CONST_Z, TAG_Zero, stnr: deststnr); |
190 | |
191 | /* sign depends upon rounding mode */ |
192 | setsign(dest, ((control_w & CW_RC) != RC_DOWN) |
193 | ? SIGN_POS : SIGN_NEG); |
194 | return TAG_Zero; |
195 | } else { |
196 | sign = signa ^ SIGN_NEG; |
197 | tag = |
198 | FPU_u_sub(arg1: b, arg2: a, answ: dest, control_w, sign, expa: expb, |
199 | expb: expa); |
200 | } |
201 | break; |
202 | case 1: /* P - N */ |
203 | tag = |
204 | FPU_u_add(arg1: a, arg2: b, answ: dest, control_w, SIGN_POS, expa, |
205 | expb); |
206 | break; |
207 | case 2: /* N - P */ |
208 | tag = |
209 | FPU_u_add(arg1: a, arg2: b, answ: dest, control_w, SIGN_NEG, expa, |
210 | expb); |
211 | break; |
212 | #ifdef PARANOID |
213 | default: |
214 | EXCEPTION(EX_INTERNAL | 0x111); |
215 | return -1; |
216 | #endif |
217 | } |
218 | if (tag < 0) { |
219 | setsign(dest, saved_sign); |
220 | return tag; |
221 | } |
222 | FPU_settagi(stnr: deststnr, tag); |
223 | return tag; |
224 | } |
225 | |
226 | if (taga == TAG_Special) |
227 | taga = FPU_Special(ptr: a); |
228 | if (tagb == TAG_Special) |
229 | tagb = FPU_Special(ptr: b); |
230 | |
231 | if (((taga == TAG_Valid) && (tagb == TW_Denormal)) |
232 | || ((taga == TW_Denormal) && (tagb == TAG_Valid)) |
233 | || ((taga == TW_Denormal) && (tagb == TW_Denormal))) { |
234 | FPU_REG x, y; |
235 | |
236 | if (denormal_operand() < 0) |
237 | return FPU_Exception; |
238 | |
239 | FPU_to_exp16(a, x: &x); |
240 | FPU_to_exp16(a: b, x: &y); |
241 | a = &x; |
242 | b = &y; |
243 | expa = exponent16(a); |
244 | expb = exponent16(b); |
245 | |
246 | goto valid_subtract; |
247 | } |
248 | |
249 | if ((taga == TW_NaN) || (tagb == TW_NaN)) { |
250 | FPU_REG const *d1, *d2; |
251 | if (flags & REV) { |
252 | d1 = b; |
253 | d2 = a; |
254 | } else { |
255 | d1 = a; |
256 | d2 = b; |
257 | } |
258 | if (flags & LOADED) |
259 | return real_2op_NaN(b, tagb, deststnr, defaultNaN: d1); |
260 | if (flags & DEST_RM) |
261 | return real_2op_NaN(b: a, tagb: taga, deststnr, defaultNaN: d2); |
262 | else |
263 | return real_2op_NaN(b, tagb, deststnr, defaultNaN: d2); |
264 | } |
265 | |
266 | return add_sub_specials(a, taga, signa, b, tagb, signb: signb ^ SIGN_NEG, |
267 | dest, deststnr, control_w); |
268 | } |
269 | |
270 | static |
271 | int add_sub_specials(FPU_REG const *a, u_char taga, u_char signa, |
272 | FPU_REG const *b, u_char tagb, u_char signb, |
273 | FPU_REG * dest, int deststnr, int control_w) |
274 | { |
275 | if (((taga == TW_Denormal) || (tagb == TW_Denormal)) |
276 | && (denormal_operand() < 0)) |
277 | return FPU_Exception; |
278 | |
279 | if (taga == TAG_Zero) { |
280 | if (tagb == TAG_Zero) { |
281 | /* Both are zero, result will be zero. */ |
282 | u_char different_signs = signa ^ signb; |
283 | |
284 | FPU_copy_to_regi(r: a, TAG_Zero, stnr: deststnr); |
285 | if (different_signs) { |
286 | /* Signs are different. */ |
287 | /* Sign of answer depends upon rounding mode. */ |
288 | setsign(dest, ((control_w & CW_RC) != RC_DOWN) |
289 | ? SIGN_POS : SIGN_NEG); |
290 | } else |
291 | setsign(dest, signa); /* signa may differ from the sign of a. */ |
292 | return TAG_Zero; |
293 | } else { |
294 | reg_copy(x: b, y: dest); |
295 | if ((tagb == TW_Denormal) && (b->sigh & 0x80000000)) { |
296 | /* A pseudoDenormal, convert it. */ |
297 | addexponent(dest, 1); |
298 | tagb = TAG_Valid; |
299 | } else if (tagb > TAG_Empty) |
300 | tagb = TAG_Special; |
301 | setsign(dest, signb); /* signb may differ from the sign of b. */ |
302 | FPU_settagi(stnr: deststnr, tag: tagb); |
303 | return tagb; |
304 | } |
305 | } else if (tagb == TAG_Zero) { |
306 | reg_copy(x: a, y: dest); |
307 | if ((taga == TW_Denormal) && (a->sigh & 0x80000000)) { |
308 | /* A pseudoDenormal */ |
309 | addexponent(dest, 1); |
310 | taga = TAG_Valid; |
311 | } else if (taga > TAG_Empty) |
312 | taga = TAG_Special; |
313 | setsign(dest, signa); /* signa may differ from the sign of a. */ |
314 | FPU_settagi(stnr: deststnr, tag: taga); |
315 | return taga; |
316 | } else if (taga == TW_Infinity) { |
317 | if ((tagb != TW_Infinity) || (signa == signb)) { |
318 | FPU_copy_to_regi(r: a, TAG_Special, stnr: deststnr); |
319 | setsign(dest, signa); /* signa may differ from the sign of a. */ |
320 | return taga; |
321 | } |
322 | /* Infinity-Infinity is undefined. */ |
323 | return arith_invalid(deststnr); |
324 | } else if (tagb == TW_Infinity) { |
325 | FPU_copy_to_regi(r: b, TAG_Special, stnr: deststnr); |
326 | setsign(dest, signb); /* signb may differ from the sign of b. */ |
327 | return tagb; |
328 | } |
329 | #ifdef PARANOID |
330 | EXCEPTION(EX_INTERNAL | 0x101); |
331 | #endif |
332 | |
333 | return FPU_Exception; |
334 | } |
335 | |