summaryrefslogblamecommitdiffstats
path: root/private/ntos/dll/i386/emfdiv.asm
blob: a06b910f2f6e74363383b9d89e5683f0aa54c7a4 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
























































































































































































































































































































































































































































































                                                                                
	subttl  emfdiv.asm - Division
	page
;*******************************************************************************
;	 Copyright (c) Microsoft Corporation 1991
;	 All Rights Reserved
;
;emfdiv.asm - long double divide
;	by Tim Paterson
;
;Purpose:
;	Long double division.
;Inputs:
;	ebx:esi = op1 mantissa
;	ecx = op1 sign in bit 15, exponent in high half
;	edi = pointer to op2 and result location
;	[Result] = edi
;
;	Exponents are unbiased.  Denormals have been normalized using
;	this expanded exponent range.  Neither operand is allowed to be zero.
;Outputs:
;	Jumps to [RoundMode] to round and store result.
;
;Revision History:
;
; []	09/05/91  TP	Initial 32-bit version.
;
;*******************************************************************************


;Dispatch tables for division
;
;One operand has been loaded into ecx:ebx:esi ("source"), the other is
;pointed to by edi ("dest").  edi points to dividend for fdiv,
;to divisor for fdivr.  
;
;Tag of source is shifted.  Tag values are as follows:
;
.erre	TAG_SNGL	eq	0	;SINGLE: low 32 bits are zero
.erre	TAG_VALID	eq	1
.erre	TAG_ZERO	eq	2
.erre	TAG_SPCL	eq	3	;NAN, Infinity, Denormal, Empty

;dest = dest / source
tFdivDisp	label	dword		;Source (reg)	Dest (*[di])
	dd	DivSingle		;single		single
	dd	DivSingle		;single		double
	dd	XorDestSign		;single		zero
	dd	DivSpclDest		;single		special
	dd	DivDouble		;double		single
	dd	DivDouble		;double		double
	dd	XorDestSign		;double		zero
	dd	DivSpclDest		;double		special
	dd	DivideByZero		;zero		single
	dd	DivideByZero		;zero		double
	dd	ReturnIndefinite	;zero		zero
	dd	DivSpclDest		;zero		special
	dd	DivSpclSource		;special	single
	dd	DivSpclSource		;special	double
	dd	DivSpclSource		;special	zero
	dd	TwoOpBothSpcl		;special	special
	dd	ReturnIndefinite	;Two infinities

;dest = source / dest
tFdivrDisp	label	dword		;Source (reg)	Dest (*[di])
	dd	DivrSingle		;single		single
	dd	DivrDouble		;single		double
	dd	DivideByZero		;single		zero
	dd	DivrSpclDest		;single		special
	dd	DivrSingle		;double		single
	dd	DivrDouble		;double		double
	dd	DivideByZero		;double		zero
	dd	DivrSpclDest		;double		special
	dd	XorSourceSign		;zero		single
	dd	XorSourceSign		;zero		double
	dd	ReturnIndefinite	;zero		zero
	dd	DivrSpclDest		;zero		special
	dd	DivrSpclSource		;special	single
	dd	DivrSpclSource		;special	double
	dd	DivrSpclSource		;special	zero
	dd	TwoOpBothSpcl		;special	special
	dd	ReturnIndefinite	;Two infinities


EM_ENTRY eFIDIV16
eFIDIV16:
	push	offset DivSetResult
	jmp	Load16Int		;Returns to DivSetResult

EM_ENTRY eFIDIVR16
eFIDIVR16:
	push	offset DivrSetResult
	jmp	Load16Int

EM_ENTRY eFIDIV32
eFIDIV32:
	push	offset DivSetResult
	jmp	Load32Int

EM_ENTRY eFIDIVR32
eFIDIVR32:
	push	offset DivrSetResult
	jmp	Load32Int

EM_ENTRY eFDIV32
eFDIV32:
	push	offset DivSetResult
	jmp	Load32Real			;Returns to DivSetResult

EM_ENTRY eFDIVR32
eFDIVR32:
	push	offset DivrSetResult		;Returns to DivrSetResult
	jmp	Load32Real

EM_ENTRY eFDIV64
eFDIV64:
	push	offset DivSetResult
	jmp	Load64Real			;Returns to DivSetResult

EM_ENTRY eFDIVR64
eFDIVR64:
	push	offset DivrSetResult
	jmp	Load64Real			;Returns to DivrSetResult


EM_ENTRY eFDIVRPreg
eFDIVRPreg:
	push	offset PopWhenDone

EM_ENTRY eFDIVRreg
eFDIVRreg:
	xchg	esi,edi

EM_ENTRY eFDIVRtop
eFDIVRtop:
	mov	ecx,EMSEG:[esi].ExpSgn
	mov	ebx,EMSEG:[esi].lManHi
	mov	esi,EMSEG:[esi].lManLo
DivrSetResult:
;cl has tag of dividend
	mov     ebp,offset tFdivrDisp
	mov	EMSEG:[Result],edi		;Save result pointer
	mov	ah,cl
	mov     al,EMSEG:[edi].bTag
	and	ah,not 1		;Ignore single vs. double on dividend
	cmp	ax,1
.erre	bTAG_VALID	eq	1
.erre	bTAG_SNGL	eq	0
	jz	DivrDouble		;Divisor was double
	ja	TwoOpResultSet
;.erre	DivrSingle eq $			;Fall into DivrSingle

;*********
DivrSingle:
;*********
;Computes op1/op2
;Op1 is double, op2 is single (low 32 bits are zero)
	mov	edx,ebx
	mov	eax,esi			;Mantissa in edx:eax
	mov	ebx,EMSEG:[edi].ExpSgn
	mov	edi,EMSEG:[edi].lManHi
	jmp	DivSingleReg


SDivBigUnderflow:
;Overflow flag set could only occur with denormals (true exp < -32768)
	or	EMSEG:[CURerr],Underflow
	test	EMSEG:[CWmask],Underflow	;Is exception masked?
	jnz	UnderflowZero		;Yes, return zero (in emfmul.asm)
	add	ecx,Underbias shl 16	;Fix up exponent
	jmp	ContSdiv		;Continue with multiply


EM_ENTRY eFDIVPreg
eFDIVPreg:
	push	offset PopWhenDone

EM_ENTRY eFDIVreg
eFDIVreg:
	xchg	esi,edi

EM_ENTRY eFDIVtop
eFDIVtop:
	mov	ecx,EMSEG:[esi].ExpSgn
	mov	ebx,EMSEG:[esi].lManHi
	mov	esi,EMSEG:[esi].lManLo
DivSetResult:
;cl has tag of divisor
	mov     ebp,offset tFdivDisp
	mov	EMSEG:[Result],edi		;Save result pointer
	mov	al,cl
	mov     ah,EMSEG:[edi].bTag
	and	ah,not 1		;Ignore single vs. double on dividend
	cmp	ax,1
.erre	bTAG_VALID	eq	1
.erre	bTAG_SNGL	eq	0
	jz	DivDouble		;Divisor was double
	ja	TwoOpResultSet
;.erre	DivSingle eq $			;Fall into DivSingle

;*********
DivSingle:
;*********
;Computes op2/op1
;Op2 is double, op1 is single (low 32 bits are zero)
	xchg	edi,ebx			;Mantissa in edi, op2 ptr to ebx
	xchg	ebx,ecx			;ExpSgn to ebx, op2 ptr to ecx
	mov	edx,EMSEG:[ecx].lManHi
	mov	eax,EMSEG:[ecx].lManLo
	mov	ecx,EMSEG:[ecx].ExpSgn	;Op2 loaded

DivSingleReg:
;dividend mantissa in edx:eax, exponent in high ecx, sign in ch bit 7
;divisor mantissa in edi, exponent in high ebx, sign in bh bit 7

	xor	ch,bh			;Compute result sign
	xor	bx,bx			;Clear out sign and tag
	sub	ecx,1 shl 16		;Exponent adjustment needed
	sub	ecx,ebx			;Compute result exponent
.erre	TexpBias eq 0			;Exponents not biased
	jo	SDivBigUnderflow	;Dividing denormal by large number
ContSdiv:

;If dividend >= divisor, the DIV instruction will overflow.  Check for
;this condition and shift the dividend right one bit if necessary.
;
;In previous versions of this algorithm for 24-bit and 53-bit mantissas,
;this shift was always performed without a test.  This meant that a 1-bit
;normalization might be required at the end.  This worked fine because
;32 or 64 bits were calculated, so extra precision was available for
;normalization.  However, this version needs all 64 bits that are calculated, 
;so we can't afford a normalization shift at the end.  This test tells us
;up front how to align so we'll be normalized.
	xor	ebx,ebx			;Extend dividend
	cmp	edi,edx			;Will DIV overflow?
	ja	DoSdiv			;No, we're safe
	shrd	ebx,eax,1
	shrd	eax,edx,1
	shr	edx,1
	add	ecx,1 shl 16		;Bump exponent to account for shift
DoSdiv:
	div	edi
	xchg	ebx,eax			;Save quotient in ebx, extend remainder
	div	edi
	mov	esi,eax
;We have a 64-bit quotient in ebx:esi.  Now compare remainder*2 with divisor
;to compute round and sticky bits.
	mov	eax,-1			;Set round and sticky bits
	shl	edx,1			;Double remainder
	jc	RoundJmp		;If too big, round & sticky set
	cmp	edx,edi			;Is remainder*2 > divisor?
	ja	RoundJmp

;Observe, oh wondering one, how you can assume the result of this last
;compare is not equality.  Use the following notation: n=numerator,
;d=denominator,q=quotient,r=remainder,b=base(2^64 here).  If
;initially we had n < d then there was no shift and we will find q and r
;so that q*d+r=n*b, if initially we had n >= d then there was a shift and
;we will find q and r so that q*d+r=n*b/2.  If we have equality here
;then r=d/2  ==>  n={possibly 2*}(2*q+1)*d/(2*b), since this can only
;be integral if d is a multiple of b, but by definition b/2 <= d < b, we
;have a contradiction.	Equality is thus impossible at this point.

	cmp	edx,1			;Check for zero remainder
	sbb	eax,-2			;eax==0 if CY, ==1 if NC (was -1)
RoundJmp:
	jmp	EMSEG:[RoundMode]

;*******************************************************************************

DDivBigUnderflow:
;Overflow flag set could only occur with denormals (true exp < -32768)
	or	EMSEG:[CURerr],Underflow
	test	EMSEG:[CWmask],Underflow	;Is exception masked?
	jnz	UnderflowZero		;Yes, return zero (in emfmul.asm)
	add	ecx,Underbias shl 16	;Fix up exponent
	jmp	ContDdiv		;Continue with multiply

DivrDoubleSetFlag:
;Special entry point used by FPATAN to set bit 6 of flag dword pushed
;on stack before call.
	or	byte ptr [esp+4],40H
;*********
DivrDouble:
;*********
;Computes op1/op2
	mov	edx,ebx
	mov	eax,esi			;Mantissa in edx:eax
	mov	ebx,EMSEG:[edi].ExpSgn
	mov	esi,EMSEG:[edi].lManHi
	mov	edi,EMSEG:[edi].lManLo
	jmp	short DivDoubleReg

HighHalfEqual:
;edx:eax:ebp = dividend
;esi:edi = divisor
;ecx = exponent and sign of result
;
;High half of dividend is equal to high half of divisor.  This will cause
;the DIV instruction to overflow.  If whole dividend >= whole divisor, then
;we just shift the dividend right 1 bit.
	cmp	eax,edi			;Is dividend >= divisor?
	jae	ShiftDividend		;Yes, divide it by two
;DIV instruction would overflow, so skip it and calculate the effective
;result.  Assume a quotient of 2^32-1 and calculate the remainder.  See
;detailed comments under MaxQuo below--this is a copy of that code.
	push	ecx			;Save exp. and sign
	mov	ebx,-1			;Max quotient digit
	sub	eax,edi			;Calculate correct remainder
;Currently edx == esi, but the next instruction ensures that is no longer
;true, since eax != 0.  This will allow us to skip the MaxQuo check at
;DivFirstDigit.
	add	edx,eax			;Should set CY if quotient fit
	mov	eax,edi			;ecx:eax has new remainder
	jc	ComputeSecond		;Remainder was positive
;Quotient doesn't fit.  Note that we can no longer ensure that edx != esi
;after making a correction.
	mov	ecx,edx			;Need remainder in ecx:eax
	jmp	DivCorrect1

;*********
DivDouble:
;*********
;Computes op2/op1
	mov	eax,edi			;Move op2 pointer
	mov	edi,esi
	mov	esi,ebx			;Mantissa in esi:edi
	mov	ebx,ecx			;ExpSgn to ebx
	mov	ecx,EMSEG:[eax].ExpSgn	;Op2 loaded
	mov	edx,EMSEG:[eax].lManHi
	mov	eax,EMSEG:[eax].lManLo

DivDoubleReg:
;dividend mantissa in edx:eax, exponent in high ecx, sign in ch bit 7
;divisor mantissa in esi:edi, exponent in high ebx, sign in bh bit 7

	xor	ch,bh			;Compute result sign
	xor	bx,bx			;Clear out sign and tag
	sub	ecx,1 shl 16		;Exponent adjustment needed
	sub	ecx,ebx			;Compute result exponent
.erre	TexpBias eq 0			;Exponents not biased
	jo	DDivBigUnderflow	;Dividing denormal by large number
ContDdiv:

;If dividend >= divisor, we must shift the dividend right one bit.
;This will ensure the result is normalized.
;
;In previous versions of this algorithm for 24-bit and 53-bit mantissas,
;this shift was always performed without a test.  This meant that a 1-bit
;normalization might be required at the end.  This worked fine because
;32 or 64 bits were calculated, so extra precision was available for
;normalization.  However, this version needs all 64 bits that are calculated, 
;so we can't afford a normalization shift at the end.  This test tells us
;up front how to align so we'll be normalized.
	xor	ebp,ebp			;Extend dividend
	cmp	esi,edx			;Dividend > divisor
	ja	DoDdiv
	jz	HighHalfEqual		;Go compare low halves
ShiftDividend:
	shrd	ebp,eax,1
	shrd	eax,edx,1
	shr	edx,1
	add	ecx,1 shl 16		;Bump exponent to account for shift
DoDdiv:
	push	ecx			;Save exp. and sign

;edx:eax:ebp = dividend
;esi:edi = divisor
;
;Division algorithm from Knuth vol. 2, p. 237, using 32-bit "digits":
;Guess a quotient digit by dividing two MSDs of dividend by the MSD of
;divisor.  If divisor is >= 1/2 the radix (radix = 2^32 in this case), then
;this guess will be no more than 2 larger than the correct value of that
;quotient digit (and never smaller).  Divisor meets magnitude condition 
;because it's normalized.

	div	esi			;Guess first quotient "digit"

;Check out our guess.  
;Currently, remainder in edx = dividend - (quotient * high half divisor).
;The definition of remainder is dividend - (quotient * all divisor).  So
;if we subtract (quotient * low half divisor) from edx, we'll get
;the true remainder.  If it's negative, our guess was too big.

	mov	ebx,eax			;Save quotient
	mov	ecx,edx			;Save remainder
	mul	edi			;Quotient * low half divisor
	sub	ebp,eax			;Subtract from dividend extension
	sbb	ecx,edx			;Subtract from remainder
	mov	eax,ebp			;Low remainder to eax
	jnc	DivFirstDigit		;Was quotient OK?
DivCorrect1:
	dec	ebx			;Quotient was too big
	add	eax,edi			;Add divisor back into remainder
	adc	ecx,esi
	jnc	DivCorrect1		;Repeat if quotient is still too big
DivFirstDigit:
	cmp	ecx,esi			;Would DIV instruction overflow?
	jae	short MaxQuo		;Yes, figure alternate quotient
	mov	edx,ecx			;Remainder back to edx:eax

;Compute 2nd quotient "digit"

ComputeSecond:
	div	esi			;Guess 2nd quotient "digit"
	mov	ebp,eax			;Save quotient
	mov	ecx,edx			;Save remainder
	mul	edi			;Quotient * low half divisor
	neg	eax			;Subtract from dividend extended with 0
	sbb	ecx,edx			;Subtract from remainder
	jnc	DivSecondDigit		;Was quotient OK?
DivCorrect2:
	dec	ebp			;Quotient was too big
	add	eax,edi			;Add divisor back into remainder
	adc	ecx,esi
	jnc	DivCorrect2		;Repeat if quotient is still too big
DivSecondDigit:
;ebx:ebp = quotient
;ecx:eax = remainder
;esi:edi = divisor
;Now compare remainder*2 with divisor to compute round and sticky bits.
	mov	edx,-1			;Set round and sticky bits
	shld	ecx,eax,1		;Double remainder
	jc	DDivEnd			;If too big, round & sticky set
	shl	eax,1
	sub	edi,eax
	sbb	esi,ecx			;Subtract remainder*2 from divisor
	jb	DDivEnd			;If <0, use round & sticky bits set

;Observe, oh wondering one, how you can assume the result of this last
;compare is not equality.  Use the following notation: n=numerator,
;d=denominator,q=quotient,r=remainder,b=base(2^64 here).  If
;initially we had n < d then there was no shift and we will find q and r
;so that q*d+r=n*b, if initially we had n >= d then there was a shift and
;we will find q and r so that q*d+r=n*b/2.  If we have equality here
;then r=d/2  ==>  n={possibly 2*}(2*q+1)*d/(2*b), since this can only
;be integral if d is a multiple of b, but by definition b/2 <= d < b, we
;have a contradiction.	Equality is thus impossible at this point.

;No round bit, but set sticky bit if remainder != 0.
	or	eax,ecx			;Is remainder zero?
	add	eax,-1			;Set CY if non-zero
	adc	edx,1			;edx==0 if NC, ==1 if CY (was -1)
DDivEnd:
	mov	esi,ebp			;Result in ebx:esi
	mov	eax,edx			;Round/sticky bits to eax
	pop	ecx			;Recover sign/exponent
	jmp	EMSEG:[RoundMode]


MaxQuo:
;ebx = first quotient "digit"
;ecx:eax = remainder
;esi:edi = divisor
;On exit, ebp = second quotient "digit"
;
;Come here if divide instruction would overflow. This must mean that ecx == esi,
;i.e., the high halves of the dividend and divisor are equal. Assume a result
;of 2^32-1, thus remainder = dividend - ( divisor * (2^32-1) )
; = dividend - divisor * 2^32 + divisor. Since the high halves of the dividend
;and divisor are equal, dividend - divisor * 2^32 can be computed by
;subtracting only the low halves. When adding divisor (in esi) to this, note
;that ecx == esi, and we want the result in ecx anyway.
;
;Note also that since the dividend is a previous remainder, the
;dividend - divisor * 2^32 calculation must always be negative. Thus the 
;addition of divisor back to it should generate a carry if it goes positive.

	mov	ebp,-1			;Max quotient digit
	sub	eax,edi			;Calculate correct remainder
	add	ecx,eax			;Should set CY if quotient fit
	mov	eax,edi			;ecx:eax has new remainder
	jc	DivSecondDigit		;Remainder was positive
	jmp	DivCorrect2