AOH :: JUFFA1.TXT
Norbert Juffa's Turbo Pascal 6.0 bug list 1 of 2
|
Newsgroups: comp.lang.pascal
Subject: Norbert Juffa's bug list (part 1 of 2)
Organization: University of Waterloo
.Message-ID: <1992Apr7.155651.23095@watdragon.waterloo.edu>
.Date: Tue, 7 Apr 1992 15:56:51 GMT
Norbert Juffa sent me the bug list attached below. His news software
wouldn't let him post it, so I've done it for him. His mail software wouldn't
accept my letter thanking him either, so I'll do it publicly: Thanks!
By the way, I'm just about ready to do a minor update to my own bug list
(latest release was Feb 2, I think, as TP6BUGS5.ZIP, available on
garbo.uwasa.fi). If anyone knows of any bugs in TP6 that they think aren't
on that list, please send them to me. Demonstration code would really be
appreciated; I'm really reluctant to include bugs that I can't reproduce
myself.
Duncan Murdoch
dmurdoch@watstat.waterloo.edu
From S_JUFFA@iravcl.ira.uka.de Tue Apr 7 11:18:11 1992
Subject: Turbo Pascal 6.0 buglist
Dear Duncan:
Find enclosed a compilation of all bug reports I sent to Borland until
03-28-92. As Borland never seems to get around to fix most of them, I
don't see any sense in being a beta tester, so I decided to quit. The
list is pretty much the same as the one I sent you before, however, I
have included the latest bug reports and have corrected some incorrect
observations that were in the original bug reports. I tried to post the
list in comp.lang.pascal, but it seems our NEWS system doesn't seem to
send out postings that are marked distribution world. So you can post it
on NEWS or do whatever you think is appropriate with this list. I also
send a copy of this to bugs@borland.com, which is supposedly where US
beta testers send their reports. BTW, I have uploaded a RTL replacement
to garbo that has some of the bugs mentioned in the reports fixed. The
file is TPL60N11.ZIP in the turbopas directory. Maybe you like it.
Norbert Juffa, email: S_JUFFA@IRAVCL.IRA.UKA.DE
++++++++++++++++++++++ Bug List TURBO-Pascal 6.0 ++++++++++++++++++++++++++++++
This list is a compilation of all the bug reports I sent to Borland between
10-01-90 and 03-28-92 regarding bugs in Turbo-Pascal 6.0 that have not been
fixed up till now. There were more bugs in the original release of TP 6.0,
which Borland fixed in a subsequent release of TP 6.0, so these are not
included in this list. For a more complete bug list of Turbo Pascal 6.0 bugs,
look for the list Duncan Murdoch irregularly publishes on Internet.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1. Error in coprocessor underflow exception handler on i8087
This bug has also been present in version 5.5 of the compiler.
It can lead to some really strange program behavior in pro-
grams using operations on the IEEE temporary floating point
format (EXTENDED) when the programm is executed on an 8087
or 80287. This error is not reproducable on an 80387, 80287XL
or 80486. The bug may be demonstrated by having the following
program run with an 8087/287 coprocessor:
{$A+,B+,D+,E-,F-,G-,I+,L+,N+,O-,R+,S+,V+,X-}
{$M 16384,0,655360}
PROGRAM 87BUG; { demonstrates some strange behavior on 8087/287 }
VAR X: EXTENDED; { allows storing of denormal }
L: WORD;
BEGIN
WriteLn ('Turbo-Pascal 6.0 floating point exception bug demo program');
WriteLn;
WriteLn ('Continously dividing 4e-4932 by 1.1...');
WriteLn;
X := 4e-4932; { close to smallest normalized EXTENDED number }
FOR L := 1 TO 5 DO BEGIN
X := X / 1.1;
Write (X:25);
IF L > 1 THEN { after 1st iter. underflow w/ flush to zero }
WriteLn (' should be: ', 0.0:25)
ELSE
WriteLn (' should be: ', X:25);
END;
END. {87BUG}
The output of this program will look like this when executed on a
system with an 8087/287 coprocessor:
Turbo-Pascal 6.0 floating point exception bug demo program
Continously dividing 4e-4932 by 1.1...
3.6363636363636364E-4932 should be: 3.6363636363636364E-4932
0.0000000000000000E+0000 should be: 0.0000000000000000E+0000
1.1000000000000000E+0000 should be: 0.0000000000000000E+0000
1.0000000000000000E+0000 should be: 0.0000000000000000E+0000
9.0909090909090909E-0001 should be: 0.0000000000000000E+0000
The bug can not be demonstrated using the coprocessor emulator,
which does not exhibit this error. The problem is in the denormal
exception handler for the coprocessor. Since denormalized numbers
are not supported by Turbo-Pascal, whenever a denormal is loaded
from memory into the coprocessor, it is changed to a true zero
(so called "flush to zero" response). Denormals can only be stored
to an EXTENDED type variable. When stored to DOUBLE or SINGLE type
variables, the rounding provided by the coprocessor will generate
zero.
On loading the denormal, the coprocessor raises the denormal and
underflow exceptions. Since the denormal exception is unmasked by
the Turbo-Pascal start-up code, the appropriate hardware interrupt
(INT 02,'NMI' on a PC type machine) is executed. The INT02 handler
of Turbo-Pascal 6.0 now performs the following steps:
First, it saves the coprocessor state using the FSTENV instruc-
tion of the coprocessor. The saved state includes the control
word, the status word, the tag word, and the instruction pointer
and opcode of the instruction causing the exception.
Second, it does some analysis to figure out what kind of exception
was the cause of the interrupt, since all coprocessor exceptions
will trap through the same interrupt.
Third, after the handler is sure that an denormal triggered the
interrupt, it empties the top of stack register (TOS) of the
coprocessor, which contains the unwanted denormal, by executing
a FSTP ST(0). After discarding the denormal, it loads a true zero
with FLDZ.
Finally, the handler exits through its standard exit, using FLDENV
to restore the coprocessor environment. This is were things start
to go wrong on an 8087/287. Although the TOS now contains zero,
the associated tag code for that register still holds a 'special'
tag, because the old tag word was reloaded, thus mirroring the
state of the coprocessor before the coprocessor exception trap was
taken. The tag code for the TOS should now contain a 'zero' tag.
Register contents and coprocessor tag word are not consistent at
this stage. This causes the coprocessor to ignore the next instruc-
tion involving that register, which in the above example program
is an FDIVP instruction. The divisor will be left on the copro-
cessor stack and stored to memory instead of the quotient, thus
giving 1.1 as a result in the above demonstration program.
The error described should only occur on the 8087/80287, not on
the 80387. From the 80387 on, Intel coprocessors examine tag codes
only to distinguish empty ('11'), from nonempty ('00', '01', '10')
registers.
This error should be quite easy to fix. After discarding the
denormal and replacing it with zero, the saved NDP state's
memory image must be changed to reflect the new register
contents. First, extract the value of ST from the saved
status word memory image. Then generate a mask for the TOS's
tag code such that subtracting it from the saved tag word
memory image will decrement the tag code for the TOS from '10'
(= SPECIAL) to '01' (= ZERO). The code might look like this:
.
FSTP ST(0) ; dispose unwanted denormal
FLDZ ; load zero instead
PUSH CX ; only DS, AX, and BX saved so far
MOV CL, [SavedStatusWord+1]; get saved NDP status word MSB
AND CL, 00111000b ; extract stack top field (0..56)
SHR CL, 1 ; generate rotate
SHR CL, 1 ; counter (0,2,4,..14)
MOV AX, 1 ; load initial mask
ROL AX, CL ; generate correct number for TOS
SUB [SavedTagWord], AX ; correct TOS tag code
POP CX ; don't need it any longer
.
.
2. Error in EXTENDED to string conversion (Str, Write)
There is an error in the internal conversion routine Float2Str
that converts an EXTENDED number to a string of decimal digits.
This bug causes some NANs to be printed as INFs. The code in
the routine fails to do a complete check on the mantissa to
figure out if it is an INF. It checks only the sixteen most
significant mantissa bits. Therefore, NANs with mantissa
between 800000000001h and 8000FFFFFFFFh are printed as INF.
The following program demonstrates the bug:
{$N+,E+}
PROGRAM INFBug;
VAR X: EXTENDED;
XA: ARRAY [1..5] OF WORD ABSOLUTE X;
BEGIN
WriteLn ('Testing correct printing of NANs');
XA [5] := $7FFF;
XA [4] := $8000;
XA [3] := $0000;
XA [2] := $0000;
XA [1] := $0001;
WriteLn ('First NAN (7FFF 8000 0000 0000 0001) prints as: ', X);
XA [5] := $FFFF;
XA [4] := $8000;
XA [3] := $0000;
XA [2] := $8000;
XA [1] := $0000;
WriteLn ('Second NAN (FFFF 8000 0000 8000 0000) prints as: ', X);
XA [5] := $7FFF;
XA [4] := $8000;
XA [3] := $4000;
XA [2] := $0000;
XA [1] := $0000;
WriteLn ('Third NAN (7FFF 8000 4000 0000 0000) prints as: ', X);
END.
The following is an excerpt from the Float2Str routine showing the
faulty code that causes the bug:
CMP AX,7FFFH ; INF or NAN ? (check exponent)
JNE @@10 ; no, Normal
CMP Value.w6,8000H ; INF ? <----- incomplete check !
JE @@3 ; yes, print INF
MOV AX,'AN' ; no, print NAN
STOSW
MOV AL,'N'
STOSB
This fragment should be replaced by the following code which
eliminates the bug:
CMP AX,7FFFH ; NAN or INF ? (check exponent)
JNE @@10 ; no, Normal
MOV DX, Value.w0 ; if any of
OR DX, Value.w2 ; last 48 mantissa bits
OR DX, Value.w4 ; is not zero,
JNZ @3a ; must be NAN
CMP Value.w6, 8000H ; INF ?
JE @@3 ; yes, print INF
@3a:
MOV AX,'AN' ; no, print NAN
STOSW
MOV AL,'N'
STOSB
3. Bug in string -> LONGINT conversion
The VAL and READ procedures for LONGINTs do not allow the smallest
LONGINT number -2147483648 (-2^31) to be read in decimal form. It
can be entered in hexadecimal form though. VAL and READ should be
changed to allow all valid LONGINTs to be read, especially since
this would not slow down the conversion process if done properly.
4. Bug in Random function for $N+ state
The Random function in programs compiled with $N+ can return the
number 1, although Random is specified to deliver values strictly
smaller than 1. This error occurs since the unsigned 32-bit integer
delivered by the random number generator is read into the coprocessor
as a signed 32-bit integer. To avoid negativ numbers, the absolute
value is taken after that before the number is divided by 2^31. If
however, the 32-bit integer delivered by the random number generator
is 80000000h it will be converted to 2^31 by taking the absolute
value in the coprocessor. Division by 2^31 will then return 1. The
Random routine should be changed to read the 32-bit integers in
a 64-bit format, thus avoiding the negativ number problem and the
FABS.
5. Differences in compile-time and run-time evaluation of certain functions
It seems that at least the Round function behaves differently at
compile time as compared to run time evaluation as demonstrated
by the following program:
{$N+,E+}
PROGRAM RoundBug;
CONST Y = 4.5;
J = Round (Y);
VAR I: INTEGER;
X: EXTENDED;
BEGIN
X := Y;
I := Round (X);
WriteLn (I:5, J:5);
END.
One would expect I and J to be equal in the output, but actual
output is:
4 5
The problem here is that the run-time version of Round uses the
Coprocessor/Emulator which provides correct IEEE-754 rounding to
nearest or even, while the compile time version of Round uses the
REAL software arithmetic with simple round to nearest or up. The
problem can easily be solved by implementing correct IEEE style
rounding for REAL arithmetic, which is strongly recommended
regardless of the bug given above.
6. Documentation enhancement needed with regard to Sin/Cos functions
When in $N+ mode, a call to the Sin and Cos functions with an
argument whose absolute value is > 2^63 = 9.22e18 will result
in an error. This makes sense, since a total loss of precision
will occur outside this range. However, the current version of
the documentation does not document this type of error.
In addition, the REAL type software arithmetic ($N-) will return
zero for the sine and cosine of large arguments. It does not
raise an error. When doing REAL computations in $N+ mode, an
error occurs in these cases. This difference in REAL arithmetic
between $N+ and $N- mode should be explained in the TP 6.0 manuals.
7. Errors in REAL type software arithmetic
There is an error in the REAL-Add/Subtract routine of TP6.0
runtime library, that may cause results to be less accurate
than would be possible. Before shifting the smaller operand's
mantissa to the right for alignment prior to mantissa addition,
a test is performed wether the shift would be for more than
39 bits. It is assumed that any shift count >= 40 would make
the second operand so small that it cannot affect the result.
This was true as long Turbo-Pascal truncated results in REAL
arithmetic, which was the case up to and including version 5.0
of the compiler. Due to the rounding introduced with TP 5.5
the above assumption no longer holds. Because of rounding,
which takes place at the 41st mantissa bit, a carry may pro-
pagate to the significant 40th bit of the final result. Therefore,
only when the shift counts needed for alignment is greater or
equal to 41 should the mantissa addition be skipped.
There are constant errors in the REAL-Exp and REAL-Ln routines
that cause some uncessary inaccuracies in the function results.
The constant Sqrt(2) in EXP should be coded as 81 FA 33 F3 04 35,
not as 81 FB 33 F3 04 35, since the mantissa to six bytes of
accuracy would be 0.3504F333F9DE. Likewise, the constant 0.5*Sqrt(2)
used by LN should be 80 FA 33 F3 04 35.
The REAL-Exp function returns with a runtime error 205 (overflow),
when called with an argument smaller than about -88.029. However,
even an argument of -88.72 would still deliver a result bigger than
the smallest normalized REAL number, which is 2^-128 or 2.94e-39.
Thus, Exp does not make use of the available argument range. Exp
should not abort with an error when called with very small arguments
anyhow. Since the exponential function approaches zero as the argument
approaches negative infinity, it should simply return zero when the
result is too small to be represented as a normalized real number.
This behavior of Exp would be consistent with the rest of the REAL
operations, since REAL arithmetic always takes the "flush to zero"
approach when results underflow.
8. Error in REAL-type multiplication
The multiplication routine for REAL type software arithmetic
provides a faster multiplication if all but the first sixteen
bits of the mantissa in one of the factors is zero. This makes
multiplication much faster when a floating point number is to
be multiplied by a small integer, such as in 3.1415926*10, since
the converted integer does not use more than the first sixteen
mantissa bits.
This part of the multiplication contains a logical error. The
bug will introduce a relative error of at most 3e-12 in the
result, whereas all other basic arithmetical operation (with
the exception of addition, see above) are accurate to the
theoretical limit of the arithmetic (9.09e-13). The bug causes
incorrect results in about 3% of the described type of multi-
plications. The errors is caused by not using all necessary
mantissa bits in the computation. The code looks as follows:
.
.
8BC5 MOV AX, BP ; the partial product
8AC4 MOV AL, AH ; of CH * (LSB of BP) is
F6E5 MUL CH ; not taken into consideration
8BD8 MOV BX, AX ; by this computation
.
The bug can be eliminated by applying the following patch:
89C8 MOV AX, CX ; this code performs the correct
F7E5 MUL BP ; computation. Note that the
89D3 MOV BX, DX ; correct value stored in BX
90 NOP ; may exceed the corresponding
90 NOP ; value above by up to three
9. Errors in coprocessor emulator
Certain coprocessor instructions will not be correctly emulated
by the emulation package of Turbo-Pascal 6.0. This bug has also
been present in all previous versions of the emulator. The
following program will demonstrate the faulty emulation of
FDECSTP:
{$A+,B-,D+,E+,F-,G-,I-,L+,N+,O-,R-,S-,V+,X-}
{$M 16384,0,655360}
PROGRAM EMUBUG;
VAR StackPointer: BYTE;
Control87, Status87: WORD;
BEGIN
WriteLn ('Turbo Pascal 6.0 coprocessor emulator bug demo program');
WriteLn;
IF Test8087 <> 0 THEN
WriteLn ('Initializing coprocessor')
ELSE
WriteLn ('Initializing emulator');
WriteLn ('Loading pi, 1, and 0 into coprocessor / emulator');
ASM
FSTCW [Control87] { save control word as set by Turbo Pascal}
FINIT { initialize coprocessor / emulator }
FLDPI { load c, stack pointer = 7 }
FLD1 { load 1, stack pointer = 6 }
FLDZ { load 0, stack pointer = 5 }
FSTSW [Status87] { save status word containing stack pointer}
FWAIT { wait until saved }
MOV AX, [Status87] { load status word }
AND AH, 38h { extract stack pointer field }
SHR AH, 1 { make }
SHR AH, 1 { it }
SHR AH, 1 { right-aligned in byte }
MOV [StackPointer], AH{ store stack pointer value }
END;
IF Test8087 = 0 THEN
WriteLn ('emulator stack pointer now: ', StackPointer,
' should be: 5')
ELSE
WriteLn ('coprocessor stack pointer now: ', StackPointer,
' should be: 5');
WriteLn ('executing / emulating FDECSTP instruction');
ASM
FDECSTP { decrement coprocessor/emulator stack ptr }
FSTSW [Status87] { store status word containing stack ptr }
FWAIT { wait until stored }
MOV AH, BYTE PTR [Status87+1] { get status word MSB }
AND AH, 38h { isolate stack ptr field in status word }
SHR AH, 1 { make }
SHR AH, 1 { it left }
SHR AH, 1 { aligned in byte }
MOV [StackPointer], AH{ store stack pointer value }
END;
IF Test8087 = 0 THEN
WriteLn ('emulator stack pointer now: ', StackPointer,
' should be: 4')
ELSE
WriteLn ('coprocessor stack pointer now: ', StackPointer,
' should be: 4');
ASM
FINIT { initalize coprocessor/emulator }
FLDCW [Control87] { restore TURBO Pascal control word }
END;
END.
When the above program is run with a coprocessor, the results will
be as expected:
Turbo Pascal 6.0 coprocessor emulator bug demo program
Initializing coprocessor
Loading pi, 1, and 0 into coprocessor / emulator
coprocessor stack pointer now: 5 should be: 5
executing / emulating FDECSTP instruction
coprocessor stack pointer now: 4 should be: 4
However, when run with the emulator, strange things happen:
Turbo Pascal 6.0 coprocessor emulator bug demo program
Initializing emulator
Loading pi, 1, and 0 into coprocessor / emulator
emulator stack pointer now: 5 should be: 5
executing / emulating FDECSTP instruction
emulator stack pointer now: 6 should be: 4
It seems that at least FINCSTP and FDECSTP are incorrectly emu-
lated. Tests show that FINCSTP actually decreases the stack pointer,
while FDECSTP increases it. This is caused by faulty entries in
the jump index table for emulated opcodes D9E0 to D9FF. Exchanging
the indices for FINCSTP and FDECSTP will cause FINCSTP to function
correctly, but because of another error FDECSTP will disturb the
emulated registers of the 80x87 emulator, which it shouldn't do.
FINCSTP and FDECSTP instructions will not be generated by the
compiler. However, programs that link with modules written in
assembly language or use the new ASM directive of Turbo-Pascal 6.0
might contain them. When run with the emulator, these programs
will behave odd or might even crash. In addition, the wraparound
stack addressing provided by the coprocessor is unavailable on the
emulator. On the coprocessor, an instruction such as FADD ST(7),ST
would write to register #2 if the current stacktop was three. The
emulator computes a linear offset and tries to write to a non-
implemented register #10. In doing so, it destroys other emulator
data residing at offsets C0h to E5h in the stack segment, just
above the emulator's register file (60h to BFh). This will cause
the emulator to malfunction or will crash the program.
The best way to get rid of this bugs would be to fix the emulator
so that it correctly emulates all coprocessor instructions and the
wraparound addressing. This should prove not to be too difficult,
since the faulty instructions are among the easiest to emulate. When
figuring out which register is meant in stack top relativ addressing,
the result should be taken modulo 8 to provide correct wraparound.
This should be quite easy. Additional code needed will be at a mini-
mum.
The other solution would be to trap all unemulated or faulty
instructions (e.g. FINCSTP, FADD ST(7),ST) upon invocation of the
emulator. The emulator would then emit an error message such as
'run-time error xx, unemulated coprocessor instruction' and abort
the program. In this case, the documentation should provide explicit
information which instructions are not emulated and should not be
used if the program is to perform correctly with the emulator. Also,
all other differences between coprocessor and emulator should be
explained.
10. Deficiencies of coprocessor emulator
The emulator of Turbo Pascal 6.0 does not emulate the following
features of a physical coprocessor: precision control and rounding
control. This can be proved by running the following programs
with and without a coprocessor.
{$N+,E+}
PROGRAM PCtrlTst;
VAR B: EXTENDED;
Precision, L: WORD;
PROCEDURE SetPrecisionControl (Precision: WORD);
(* This procedure sets the internal precision of the NDP. Available *)
(* precision values: 0 - 24 bits (SINGLE) *)
(* 1 - n.a. (mapped to single) *)
(* 2 - 53 bits (DOUBLE) *)
(* 3 - 64 bits (EXTENDED) *)
VAR CtrlWord: WORD;
BEGIN {SetPrecisionCtrl}
IF Precision = 1 THEN
Precision := 0;
Precision := Precision SHL 8; { make mask for PC field in ctrl word }
ASM
FSTCW [CtrlWord] { store NDP control word }
MOV AX, [CtrlWord] { load control word into CPU }
AND AX, 0FCFFh { mask out precision control field }
OR AX, [Precision] { set desired precision in PC field }
MOV [CtrlWord], AX { store new control word }
FLDCW [CtrlWord] { set new precision control in NDP }
END;
END; {SetPrecisionCtrl}
BEGIN {main}
FOR Precision := 1 TO 3 DO BEGIN
B := 1.2345678901234567890;
SetPrecisionControl (Precision);
FOR L := 1 TO 20 DO BEGIN
B := Sqrt (B);
END;
FOR L := 1 TO 20 DO BEGIN
B := B*B;
END;
SetPrecisionControl (3); { full precision for printout }
WriteLn (Precision, B:28);
END;
END.
The output of the above program looks like this when executed
with a coprocessor present:
1 1.13311278820037842E+0000 (* single precision *)
2 1.23456789006442125E+0000 (* double precision *)
3 1.23456789012337585E+0000 (* extended precision *)
However, when executed with the emulator, output is as follows:
1 1.23456789012351396E+0000
2 1.23456789012351396E+0000
3 1.23456789012351396E+0000
Changing the value of precision control obviously has no effect at
all on the emulator. It always works with extended precision in
internal calculations. This deviation of the emulator from a real
coprocessor should be documented in the TP 6.0 User Manual.
{$N+,E+}
PROGRAM RCtrlTst;
VAR B: EXTENDED;
RoundingMode, L: WORD;
PROCEDURE SetRoundingMode (RCMode: WORD);
(* This procedure selects one of four available rounding modes *)
(* 0 - Round to nearest (default) *)
(* 1 - Round down (towards negative infinity) *)
(* 2 - Round up (towards positive infinity) *)
(* 3 - Chop (truncate, round towards zero) *)
VAR CtrlWord: WORD;
BEGIN
RCMode := RCMode SHL 10; { make mask for RC field in control word}
ASM
FSTCW [CtrlWord] { store NDP control word }
MOV AX, [CtrlWord] { load control word into CPU }
AND AX, 0F3FFh { mask out rounding control field }
OR AX, [RCMode] { set desired precision in RC field }
MOV [CtrlWord], AX { store new control word }
FLDCW [CtrlWord] { set new rounding control in NDP }
END;
END;
BEGIN
FOR RoundingMode := 0 TO 3 DO BEGIN
B := 1.2345678901234567890e100;
SetRoundingMode (RoundingMode);
FOR L := 1 TO 51 DO BEGIN
B := Sqrt (B);
END;
FOR L := 1 TO 51 DO BEGIN
B := -B*B;
END;
SetRoundingMode (0); { round to nearest for printout }
WriteLn (RoundingMode, B:28);
END;
END.
The calculations performed in the above program were selected so
that every rounding mode would lead to a distinct final value. The
output when run with a coprocessor appears below.
As expected, four different values are printed at the end of the
program if a coprocessor is present.
0 -1.23427629010100635E+0100 (* round nearest *)
1 -1.23427623555772409E+0100 (* round down *)
2 -1.23457760966801097E+0100 (* round up *)
3 -1.23397493540770643E+0100 (* chop *)
With the emulator, four identical results are produced, indicating
that the emulator does not support the IEEE rounding modes of the
coprocessor.
0 -1.23457766383395931E+0100
1 -1.23457766383395931E+0100
2 -1.23457766383395931E+0100
3 -1.23457766383395931E+0100
This deviation from the behavior of the actual coprocessor should
be mentioned in TP 6.0 documentation.
11. Deficiencies in Coprocessor emulator
The coprocessor emulator used by programs compiled in the
$N+,E+ mode when a coprocessor is absent at run-time does
not correctly handle special arguments like ZERO, INF, and
NANs. Specific problems are:
- multiplication and division with INFs resulting in NANs
instead of INFs
- 0+(-0) = -0, but (-0)+0 = 0
- operations on QNANs (quiet NaNs) signaling an exception
The following program will demonstrate the bugs:
{$N+,E+}
PROGRAM InfTest;
VAR INF, NEGINF: EXTENDED;
QNAN, SNAN: EXTENDED;
X, NEGX: EXTENDED;
Z, NEGZ: EXTENDED;
PSEUDOZERO: EXTENDED;
INFA: ARRAY [1..5] OF WORD ABSOLUTE INF;
QA: ARRAY [1..5] OF WORD ABSOLUTE QNAN;
SA: ARRAY [1..5] OF WORD ABSOLUTE SNAN;
PA: ARRAY [1..5] OF WORD ABSOLUTE PSEUDOZERO;
BEGIN
INFA [5] := $7FFF;
INFA [4] := $8000;
INFA [3] := $0000;
INFA [2] := $0000;
INFA [1] := $0000;
QA [5] := $7FFF;
QA [4] := $C000;
QA [3] := $0000;
QA [2] := $0000;
QA [1] := $0001;
SA [5] := $7FFF;
SA [4] := $8000;
SA [3] := $0000;
SA [2] := $0000;
SA [1] := $0001;
PA [5] := $52FB;
PA [4] := $0000;
PA [3] := $0000;
PA [2] := $0000;
PA [1] := $0000;
NEGINF := -INF;
X := 5;
NEGX := -5;
Z := 0;
NEGZ := -Z;
WriteLn (' INF + INF: ', INF + INF:6:0, ' should be INF');
WriteLn ('-INF + -INF: ', NEGINF + NEGINF:6:0, ' should be -INF');
WriteLn (' INF + X : ', INF + X:6:0, ' should be INF');
WriteLn (' INF - -INF: ', INF - NEGINF:6:0, ' should be INF');
WriteLn ('-INF - INF: ', NEGINF - INF:6:0, ' should be -INF');
WriteLn (' X - INF: ', X - INF:6:0, ' should be -INF');
WriteLn (' INF * INF: ', INF * INF:6:0, ' should be INF');
WriteLn ('-INF * INF: ', NEGINF * INF:6:0, ' should be -INF');
WriteLn (' INF * -INF: ', INF * NEGINF:6:0, ' should be -INF');
WriteLn ('-INF * -INF: ', NEGINF * NEGINF:6:0, ' should be INF');
WriteLn (' X * INF: ', X * INF:6:0, ' should be INF');
WriteLn (' -X * INF: ', NEGX * INF:6:0, ' should be -INF');
WriteLn (' INF / 0 : ', INF / 0:6:0, ' should be INF');
WriteLn ('-INF / 0 : ', NEGINF / 0:6:0, ' should be -INF');
WriteLn (' X / INF: ', X / INF:6:0, ' should be 0');
WriteLn (' INF / -X : ', INF / NEGX:6:0, ' should be INF');
WriteLn (' Sqrt (INF): ', Sqrt (INF):6:0, ' should be INF');
WriteLn (' -0 + -0 : ', NEGZ + NEGZ:6:0, ' should be -0');
WriteLn (' 0 + -0 : ', Z + NEGZ:6:0, ' should be 0');
WriteLn (' -0 + 0 : ', NEGZ + Z:6:0, ' should be 0');
WriteLn (' -0 * 0 : ', NEGZ * Z:6:0, ' should be -0');
WriteLn (' 0 * -0 : ', Z * NEGZ:6:0, ' should be -0');
WriteLn (' -0 * X : ', NEGZ * X:6:0, ' should be -0');
WriteLn (' X * -0 : ', X * NEGZ:6:0, ' should be -0');
WriteLn (' -X * 0 : ', NEGX * Z:6:0, ' should be -0');
WriteLn (' -X * -0 : ', NEGX * NEGZ:6:0, ' should be 0');
WriteLn (' Sqrt (-0) : ', Sqrt (NEGZ):6:0, ' should be -0');
WriteLn ('QNAN * QNAN: ', QNAN * QNAN:6:0, ' should be NAN');
WriteLn ('QNAN + QNAN: ', QNAN + QNAN:6:0, ' should be NAN');
WriteLn ('QNAN / QNAN: ', QNAN / QNAN:6:0, ' should be NAN');
WriteLn ('Sqrt (QNAN): ', Sqrt (QNAN):6:0, ' should be NAN');
END. { InfTest}
When run on an 80387 coprocessor, the output of the above program
is as follows:
INF + INF: INF should be INF
-INF + -INF: -INF should be -INF
INF + X : INF should be INF
INF - -INF: INF should be INF
-INF - INF: -INF should be -INF
X - INF: -INF should be -INF
INF * INF: INF should be INF
-INF * INF: -INF should be -INF
INF * -INF: -INF should be -INF
-INF * -INF: INF should be INF
X * INF: INF should be INF
-X * INF: -INF should be -INF
INF / 0 : INF should be INF
-INF / 0 : -INF should be -INF
X / INF: 0 should be 0
INF / -X : -INF should be INF
Sqrt (INF): INF should be INF
-0 + -0 : -0 should be -0
0 + -0 : 0 should be 0
-0 + 0 : 0 should be 0
-0 * 0 : -0 should be -0
0 * -0 : -0 should be -0
-0 * X : -0 should be -0
X * -0 : -0 should be -0
-X * 0 : -0 should be -0
-X * -0 : 0 should be 0
Sqrt (-0) : -0 should be -0
QNAN * QNAN: NAN should be NAN
QNAN + QNAN: NAN should be NAN
QNAN / QNAN: NAN should be NAN
Sqrt (QNAN): NAN should be NAN
However, when run with the emulator, the programs output looks
like this:
INF + INF: INF should be INF
-INF + -INF: -INF should be -INF
INF + X : INF should be INF
INF - -INF: INF should be INF
-INF - INF: -INF should be -INF
X - INF: -INF should be -INF
INF * INF: NAN should be INF <----- error
-INF * INF: NAN should be -INF <----- error
INF * -INF: NAN should be -INF <----- error
-INF * -INF: NAN should be INF <----- error
X * INF: NAN should be INF <----- error
-X * INF: NAN should be -INF <----- error
INF / 0 : NAN should be INF <----- error
-INF / 0 : NAN should be -INF <----- error
X / INF: 0 should be 0
INF / -X : NAN should be INF <----- error
Sqrt (INF): INF should be INF
-0 + -0 : -0 should be -0
0 + -0 : -0 should be 0 <----- error
-0 + 0 : 0 should be 0
-0 * 0 : -0 should be -0
0 * -0 : -0 should be -0
-0 * X : -0 should be -0
X * -0 : -0 should be -0
-X * 0 : -0 should be -0
-X * -0 : 0 should be 0
Sqrt (-0) : -0 should be -0
QNAN * QNAN: Runtime error 207 at 0000:09F8. <----- error
This handling of QNANs also violates the IEEE-754 standard for
binary floating point arithmetic, which states in section
6.2 (Operations with NaNs): "Every operation involving one
or two input NaNs, none of them signaling, shall signal no
exception but, if a floating-point result is to be delivered,
shall deliver as its result a quiet NaN, which should be one
of the input NaNs".
12. Error in inline assembler
The inline assembler incorrectly accepts a type name as a variable
name for memory operands, if the type size is the same as the size
of the memory operand. The following program fragment is legal in
the current version of the inline assembler:
ASM
...
MOV AX, [WORD]
MOV AL, [BYTE]
LES DI, [LONGINT]
...
END;
The address generated for the memory operands in these cases is
always 0. This bug should be fixed immediately.
13. Error in inline assembler
The PTR operator of the inline assembler will incorrectly accept
an expression of class register if the size of the register is
the same as the type it is casted to. The following statements are
legal in the current version of the inline assembler:
ASM
...
MOV BYTE PTR AL, 5
ADD WORD PTR AX, 5
MOV WORD PTR ES, 6
...
END;
The assembler will generate memory references to memory location
0 in these cases, e.g. MOV BYTE PTR [0], 5. The PTR operator
should be fixed to only accept expressions of class memory.
14. Possible problems arising out of the use of the SEG and OFFSET
operators with local variables
Use of the SEG operator with local variables and parameters is
allowed in the inline assembler. One would expect that the SEG
value of a local (auto) variable is the value of the stack
segment (SS), just as the SEG value of a global (static) variable
is the data segment (DS, @Data). However, the value stored by the
inline assembler seems always to be 0. Applying the SEG operator
to local variables should either be forbidden, or the correct
value should be supplied by the assembler.
The Programmer's Guide states that the OFFSET of local variables,
parameters, and the @Result symbol is the offset relative to
the framepointer of the entity in which they were declared. This
works as expected, but there is a problem when nested procedures/
functions are used. Although the local variables of an outer
procedural level are visible to inner procedures, no meaningful
OFFSET value can be computed for these variables relative to
the framepointer of the inner procedure. Therefore, the assembler
should either forbid references to local variables declared at an
outer level or generate the code necessary to address across the
several levels of indirection involved. The following example
illustrates the problem:
FUNCTION Outer (R: WORD): WORD; { copies input R to function output }
FUNCTION Inner (W: WORD): WORD; ASSEMBLER; {should copy R to func. output}
ASM
MOV AX, R { will not load R !!}
END;
BEGIN { Outer }
R := Inner (R); { does *not* copy R to R !! }
ASM
MOV DI, OFFSET R { load offset of R relative to Outer's BP}
MOV AX, [BP+DI] { load parameter R }
MOV SI, OFFSET @Result { load offset of Outer's result }
MOV [BP+SI], AX { store value of R into Outer's result }
END;
END;
BEGIN
WriteLn (Outer(5));
END.
Instead of printing '5', as one would expect, this program prints a
value like '9094' depending on the initial stack size set in the
TP configuration.
15. Error when using the WITH directive with the inline assembler
When accessing parts of a record using inline assembler from within
a WITH block, the inline assembler doesn't correctly compute the
addresses of the record's parts. It uses the offset of the record
part within the record as the address. However, the base address of
the record must be added to this value to get the correct address
of the record part. Writing to record parts using the inline assembler
from within a WITH block will destroy other data in the data segment.
The following program illustrates the problem. It first initializes
a record using the inline assembler without making use of a WITH
block. It prints the contents of the record, then updates it using
inline assembler from within a WITH block and print the record again.
If the inline assembler worked correctly, two different printouts
would be the result. Actually, the second record update doesn't
change the record but destroys other data in the data segment.
Therefore, the same data is printed out twice.
{$A+,N-,I-,S-,R-,B-}
PROGRAM WITHBug;
VAR Student: RECORD
ID: LONGINT;
Name: STRING;
GPA: REAL;
END;
BEGIN
{ first student: ID = 12345678, Name = JOHN, GPA = 1.0 }
ASM
MOV WORD PTR [Student.ID], 5678
MOV WORD PTR [Student.ID+2], 1234
MOV WORD PTR [Student.GPA], 81h
MOV WORD PTR [Student.GPA+2], 0
MOV WORD PTR [Student.GPA+4], 0
MOV BYTE PTR [Student.Name], 4
MOV WORD PTR [Student.Name+1], 'OJ'
MOV WORD PTR [Student.Name+3], 'NH'
END;
WriteLn ('Student''s ID: ', Student.ID);
WriteLn (' Name: ', Student.Name);
WriteLn (' GPA: ', Student.GPA:0:2);
{ second student: ID = 87654321, Name = JANE, GPA = 2.0 }
WITH Student DO
ASM
MOV WORD PTR [ID], 4321
MOV WORD PTR [ID+2], 8765
MOV WORD PTR [GPA], 82h
MOV WORD PTR [GPA+2], 0
MOV WORD PTR [GPA+4], 0
MOV BYTE PTR [Name], 4
MOV WORD PTR [Name+1], 'AJ'
MOV WORD PTR [Name+3], 'EN'
END;
WriteLn ('Student''s ID: ', Student.ID);
WriteLn (' Name: ', Student.Name);
WriteLn (' GPA: ', Student.GPA:0:2);
END.
The following excerpts from the resulting code show the error:
1738:003B C70644002E16 MOV Word Ptr [0044],162E ; 1st record
1738:0041 C7064600D204 MOV Word Ptr [0046],04D2 ; initialization
1738:0047 C70648018100 MOV Word Ptr [0148],0081 ; using
1738:004D C7064A010000 MOV Word Ptr [014A],0000 ; correct
1738:0053 C7064C010000 MOV Word Ptr [014C],0000 ; addresses
1738:0059 C606480004 MOV Byte Ptr [0048],04
1738:005E C70649004A4F MOV Word Ptr [0049],4F4A
1738:0064 C7064B00484E MOV Word Ptr [004B],4E48
1738:00E4 C7060000E110 MOV Word Ptr [0000],10E1 ; 2nd record
1738:00EA C70602003D22 MOV Word Ptr [0002],223D ; initialization
1738:00F0 C70604018200 MOV Word Ptr [0104],0082 ; using offsets
1738:00F6 C70606010000 MOV Word Ptr [0106],0000 ; into record
1738:00FC C70608010000 MOV Word Ptr [0108],0000 ; instead of
1738:0102 C606040004 MOV Byte Ptr [0004],04 ; addresses
1738:0107 C70605004A41 MOV Word Ptr [0005],414A
1738:010D C70607004E45 MOV Word Ptr [0007],454E
The entire AOH site is optimized to look best in Firefox® 3 on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986- AOH
We do not send spam. If you have received spam bearing an artofhacking.com email address, please forward it with full headers to abuse@artofhacking.com.