From: dmurdoch@watstat.waterloo.edu (Duncan Murdoch)
Subject: Norbert Juffa's bug list (part 1 of 2)
Organization: University of Waterloo
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