Commit | Line | Data |
---|---|---|
1da177e4 LT |
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, | |
3d0d14f9 | 30 | FPU_REG * dest, int deststnr, int control_w); |
1da177e4 LT |
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 | { | |
3d0d14f9 IM |
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 | } | |
1da177e4 | 84 | } |
1da177e4 | 85 | |
3d0d14f9 IM |
86 | if (tag < 0) { |
87 | setsign(dest, saved_sign); | |
88 | return tag; | |
89 | } | |
90 | FPU_settagi(deststnr, tag); | |
91 | return tag; | |
1da177e4 | 92 | } |
1da177e4 | 93 | |
3d0d14f9 IM |
94 | if (taga == TAG_Special) |
95 | taga = FPU_Special(a); | |
96 | if (tagb == TAG_Special) | |
97 | tagb = FPU_Special(b); | |
1da177e4 | 98 | |
3d0d14f9 | 99 | if (((taga == TAG_Valid) && (tagb == TW_Denormal)) |
1da177e4 | 100 | || ((taga == TW_Denormal) && (tagb == TAG_Valid)) |
3d0d14f9 IM |
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 | } | |
1da177e4 | 115 | |
3d0d14f9 IM |
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 | } | |
1da177e4 | 122 | |
3d0d14f9 IM |
123 | return add_sub_specials(a, taga, signa, b, tagb, signb, |
124 | dest, deststnr, control_w); | |
1da177e4 LT |
125 | } |
126 | ||
1da177e4 LT |
127 | /* Subtract b from a. (a-b) -> dest */ |
128 | int FPU_sub(int flags, int rm, int control_w) | |
129 | { | |
3d0d14f9 IM |
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; | |
1da177e4 LT |
148 | } |
149 | ||
3d0d14f9 IM |
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; | |
1da177e4 | 211 | #ifdef PARANOID |
3d0d14f9 IM |
212 | default: |
213 | EXCEPTION(EX_INTERNAL | 0x111); | |
214 | return -1; | |
1da177e4 | 215 | #endif |
3d0d14f9 IM |
216 | } |
217 | if (tag < 0) { | |
218 | setsign(dest, saved_sign); | |
219 | return tag; | |
220 | } | |
221 | FPU_settagi(deststnr, tag); | |
222 | return tag; | |
1da177e4 | 223 | } |
1da177e4 | 224 | |
3d0d14f9 IM |
225 | if (taga == TAG_Special) |
226 | taga = FPU_Special(a); | |
227 | if (tagb == TAG_Special) | |
228 | tagb = FPU_Special(b); | |
1da177e4 | 229 | |
3d0d14f9 | 230 | if (((taga == TAG_Valid) && (tagb == TW_Denormal)) |
1da177e4 | 231 | || ((taga == TW_Denormal) && (tagb == TAG_Valid)) |
3d0d14f9 IM |
232 | || ((taga == TW_Denormal) && (tagb == TW_Denormal))) { |
233 | FPU_REG x, y; | |
1da177e4 | 234 | |
3d0d14f9 IM |
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); | |
1da177e4 | 244 | |
3d0d14f9 | 245 | goto valid_subtract; |
1da177e4 | 246 | } |
3d0d14f9 IM |
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); | |
1da177e4 | 263 | } |
1da177e4 | 264 | |
3d0d14f9 IM |
265 | return add_sub_specials(a, taga, signa, b, tagb, signb ^ SIGN_NEG, |
266 | dest, deststnr, control_w); | |
267 | } | |
1da177e4 LT |
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, | |
3d0d14f9 | 272 | FPU_REG * dest, int deststnr, int control_w) |
1da177e4 | 273 | { |
3d0d14f9 IM |
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; | |
1da177e4 | 327 | } |
1da177e4 | 328 | #ifdef PARANOID |
3d0d14f9 | 329 | EXCEPTION(EX_INTERNAL | 0x101); |
1da177e4 LT |
330 | #endif |
331 | ||
3d0d14f9 | 332 | return FPU_Exception; |
1da177e4 | 333 | } |