home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #19 / NN_1992_19.iso / spool / comp / lang / pascal / 5169 < prev    next >
Encoding:
Text File  |  1992-09-02  |  97.6 KB  |  2,272 lines

  1. Path: sparky!uunet!cs.utexas.edu!qt.cs.utexas.edu!yale.edu!ira.uka.de!uka!uka!news
  2. From: S_JUFFA@iravcl.ira.uka.de (|S| Norbert Juffa)
  3. Newsgroups: comp.lang.pascal
  4. Subject: Turbo Pascal 6.0 bug list (long!)
  5. Date: 2 Sep 1992 13:41:57 GMT
  6. Organization: University of Karlsruhe (FRG) - Informatik Rechnerabt.
  7. Lines: 2259
  8. Distribution: world
  9. Message-ID: <182gb5INNsm4@iraul1.ira.uka.de>
  10. NNTP-Posting-Host: irav21.ira.uka.de
  11. X-News-Reader: VMS NEWS 1.23
  12.  
  13. y++++++++++++++++++++++ Bug List TURBO-Pascal 6.0 ++++++++++++++++++++++++++++++
  14.  
  15. This list is a compilation of all the bug reports I (Norbert Juffa, email:
  16. S_JUFFA@IRAVCL.IRA.UKA.DE) sent to Borland between 10-01-90 and 07-28-92
  17. regarding bugs in Turbo-Pascal 6.0 that have not been fixed up till now.
  18. There were more bugs in the original release of TP 6.0, which Borland fixed
  19. in a subsequent release of TP 6.0, so these are not included in this list.
  20. For a more complete bug list of Turbo Pascal 6.0 bugs, look for the list
  21. Duncan Murdoch (dmurdoch@mast.queensu.ca) irregularly publishes on
  22. Internet. If you find any bug in TP 6.0, be it in the compiler, run-time
  23. library or Turbo Vision, please send a description of the bug to Duncan.
  24. Include a demonstration program that reliably reproduces the bug whenever
  25. possible.
  26.  
  27. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  28.  
  29.  
  30. 1. Error in coprocessor underflow exception handler on i8087
  31.  
  32.      This bug has also been present in version 5.5 of the compiler.
  33.      It can lead to some really strange program behavior in pro-
  34.      grams using operations on the IEEE temporary floating point
  35.      format (EXTENDED) when the programm is executed on an 8087
  36.      or 80287. This error is not reproducable on an 80387, 80287XL
  37.      or 80486. The bug may be demonstrated by having the following
  38.      program run with an 8087/287 coprocessor:
  39.  
  40.  
  41.      {$A+,B+,D+,E-,F-,G-,I+,L+,N+,O-,R+,S+,V+,X-}
  42.      {$M 16384,0,655360}
  43.  
  44.      PROGRAM 87BUG;        { demonstrates some strange behavior on 8087/287 }
  45.  
  46.      VAR X: EXTENDED;      { allows storing of denormal }
  47.          L: WORD;
  48.  
  49.      BEGIN
  50.        WriteLn ('Turbo-Pascal 6.0 floating point exception bug demo program');
  51.        WriteLn;
  52.        WriteLn ('Continously dividing 4e-4932 by 1.1...');
  53.        WriteLn;
  54.        X := 4e-4932;       { close to smallest normalized EXTENDED number }
  55.        FOR L := 1 TO 5 DO BEGIN
  56.           X := X / 1.1;
  57.           Write (X:25);
  58.           IF L > 1 THEN    { after 1st iter. underflow w/ flush to zero }
  59.              WriteLn ('   should be: ', 0.0:25)
  60.           ELSE
  61.              WriteLn ('   should be: ', X:25);
  62.        END;
  63.      END. {87BUG}
  64.  
  65.  
  66.      The output of this program will look like this when executed on a
  67.      system with an 8087/287 coprocessor:
  68.  
  69.  
  70.      Turbo-Pascal 6.0 floating point exception bug demo program
  71.  
  72.      Continously dividing 4e-4932 by 1.1...
  73.  
  74.      3.6363636363636364E-4932   should be:  3.6363636363636364E-4932
  75.      0.0000000000000000E+0000   should be:  0.0000000000000000E+0000
  76.      1.1000000000000000E+0000   should be:  0.0000000000000000E+0000
  77.      1.0000000000000000E+0000   should be:  0.0000000000000000E+0000
  78.      9.0909090909090909E-0001   should be:  0.0000000000000000E+0000
  79.  
  80.  
  81.      The bug can not be demonstrated using the coprocessor emulator,
  82.      which does not exhibit this error. The problem is in the denormal
  83.      exception handler for the coprocessor. Since denormalized numbers
  84.      are not supported by Turbo-Pascal, whenever a denormal is loaded
  85.      from memory into the coprocessor, it is changed to a true zero
  86.      (so called "flush to zero" response). Denormals can only be stored
  87.      to an EXTENDED type variable. When stored to DOUBLE or SINGLE type
  88.      variables, the rounding provided by the coprocessor will generate
  89.      zero.
  90.  
  91.      On loading the denormal, the coprocessor raises the denormal and
  92.      underflow exceptions. Since the denormal exception is unmasked by
  93.      the Turbo-Pascal start-up code, the appropriate hardware interrupt
  94.      (INT 02,'NMI' on a PC type machine) is executed. The INT02 handler
  95.      of Turbo-Pascal 6.0 now performs the following steps:
  96.      First, it saves the coprocessor state using the FSTENV instruc-
  97.      tion of the coprocessor. The saved state includes the control
  98.      word, the status word, the tag word, and the instruction pointer
  99.      and opcode of the instruction causing the exception.
  100.      Second, it does some analysis to figure out what kind of exception
  101.      was the cause of the interrupt, since all coprocessor exceptions
  102.      will trap through the same interrupt.
  103.      Third, after the handler is sure that an denormal triggered the
  104.      interrupt, it empties the top of stack register (TOS) of the
  105.      coprocessor, which contains the unwanted denormal, by executing
  106.      a FSTP ST(0). After discarding the denormal, it loads a true zero
  107.      with FLDZ.
  108.      Finally, the handler exits through its standard exit, using FLDENV
  109.      to restore the coprocessor environment. This is were things start
  110.      to go wrong on an 8087/287. Although the TOS now contains zero,
  111.      the associated tag code for that register still holds a 'special'
  112.      tag, because the old tag word was reloaded, thus mirroring the
  113.      state of the coprocessor before the coprocessor exception trap was
  114.      taken. The tag code for the TOS should now contain a 'zero' tag.
  115.      Register contents and coprocessor tag word are not consistent at
  116.      this stage. This causes the coprocessor to ignore the next instruc-
  117.      tion involving that register, which in the above example program
  118.      is an FDIVP instruction. The divisor will be left on the copro-
  119.      cessor stack and stored to memory instead of the quotient, thus
  120.      giving 1.1 as a result in the above demonstration program.
  121.      The error described should only occur on the 8087/80287, not on
  122.      the 80387. From the 80387 on, Intel coprocessors examine tag codes
  123.      only to distinguish empty ('11'), from nonempty ('00', '01', '10')
  124.      registers.
  125.  
  126.      This error should be quite easy to fix. After discarding the
  127.      denormal and replacing it with zero, the saved NDP state's
  128.      memory image must be changed to reflect the new register
  129.      contents. First, extract the value of ST from the saved
  130.      status word memory image. Then generate a mask for the TOS's
  131.      tag code such that subtracting it from the saved tag word
  132.      memory image will decrement the tag code for the TOS from '10'
  133.      (= SPECIAL) to '01' (= ZERO). The code might look like this:
  134.  
  135.      .
  136.      FSTP    ST(0)                  ; dispose unwanted denormal
  137.      FLDZ                           ; load zero instead
  138.      PUSH    CX                     ; only DS, AX, and BX saved so far
  139.      MOV     CL, [SavedStatusWord+1]; get saved NDP status word MSB
  140.      AND     CL, 00111000b          ; extract stack top field (0..56)
  141.      SHR     CL, 1                  ; generate rotate
  142.      SHR     CL, 1                  ;  counter (0,2,4,..14)
  143.      MOV     AX, 1                  ; load initial mask
  144.      ROL     AX, CL                 ; generate correct number for TOS
  145.      SUB     [SavedTagWord], AX     ; correct TOS tag code
  146.      POP     CX                     ; don't need it any longer
  147.      .
  148.      .
  149.  
  150.  
  151. 2. Bugs in the handling of denormal numbers of types SINGLE, DOUBLE,
  152.    EXTENDED (IEEE numeric data types)
  153.  
  154.      One of the important features of the IEEE-754 Standard for Binary
  155.      Floating Point Arithmetic [1] is that it demands the implementation
  156.      of denormal numbers. If the result of a computation can not be
  157.      represented as a normalized number (based exponent > 0, mantissa 1
  158.      <= mantissa < 2) there is an *underflow* condition. The required
  159.      response from an IEEE-754 compliant system to an underflow is the
  160.      generation of a denormal number, if that is at all possible (the
  161.      number could be too small to be represented even as a denormal)
  162.      Denormal numbers have a based exponent of zero, the mantissa
  163.      can have any value between 01....0 and 00....1. Turbo Pascal 6.0
  164.      generally supports denormal numbers for it's IEEE floating point
  165.      types SINGLE, DOUBLE, and EXTENDED. However, there are some bugs
  166.      and undocumented features involved on the handling of denormals.
  167.  
  168.      1) Coprocessor emulator does not support denormal numbers
  169.         Presumably for performance reasons, support for denormals
  170.         of any IEEE data types is not included in the coprocessor
  171.         emulator. If the result of an operation cannot be represented
  172.         as a normalized number, the emulator will convert it to zero
  173.         ("flush to zero" response). This is one of the many instances
  174.         where the emulator differs from a real coprocessor and which
  175.         is *not* documented in the manuals. The emulator's inability
  176.         to support denormals can result in unexplainable differences
  177.         in the results of the same computation depending on wether
  178.         the emulator or a real coprocessor was used. There are
  179.         real applications that underflow quite frequently, so the
  180.         support of gradual underflow with the help of denormals can
  181.         make for differences in the final results [2]. The inability
  182.         of the emulator to support denormals should be flagged as a
  183.         bug.
  184.  
  185.      2) There is an undocumented quirk in the handling of denormalized
  186.         numbers when using a program compiled with $N+ with a real
  187.         coprocessor. On a 8087 and the original 80287, denormal numbers
  188.         are only supported for the SINGLE and DOUBLE types, EXTENDED
  189.         type denormals are flushed to zero upon being loaded. On the
  190.         80287XL, 80387, and 80486 denormals are supported for SINGLE,
  191.         DOUBLE and EXTENDED. This difference is due to differences in
  192.         the coprocessors and how Turbo Pascal initializes them.
  193.         On a 8087/80287 loading a denormal of any data type will raise
  194.         a denormalized number exception. For a 8087/287 TP has this
  195.         exception unmasked. The expection response for the 8087/287
  196.         in TP is to normalize SINGLE and DOUBLE numbers in the
  197.         internal (EXTENDED) format (note that the 8087/287 do not do
  198.         this automatically) and to flush EXTENDED denormals to zero
  199.         (probably because denormals in the internal format give rise
  200.         to further complications on a 8087/287, especially since the
  201.         denormal exception is unmasked, which it has to be to enable
  202.         at least correct operation on SINGLE and DOUBLE denormals).
  203.         On a 80287XL/387/486 loading a SINGLE or DOUBLE denormal will
  204.         raise a denormalized number exception, while loading a EXTENDED
  205.         denormal will *not* raise that exception [3]. For these
  206.         coprocessors, TP has the denormal exception masked. The masked
  207.         response to a denormal exception on these coprocessors is to
  208.         automatically normalize SINGLE and DOUBLE denormals in the
  209.         internal (EXTENDED) format. Since the denormal exception is
  210.         masked and the handling of denormals has been enhanced for
  211.         the 287XL/387/486, operations on EXTENDED denormals are safe
  212.         on these coprocessors.
  213.         The approach chosen for TP is reasonable. Computational results
  214.         can vary depending on the coprocessor used, though. Therefore,
  215.         the different handling of denormals for 8087/287 and 80287XL/
  216.         387/486 must be documented in TP's manuals.
  217.  
  218.      3) There is a bug in the float to string conversion routine in
  219.         the TP 6.0 run time library that causes EXTENDED denormals
  220.         to be printed as zero, even on coprocessors where EXTENDED
  221.         denormals are supported by TP 6.0. This is caused by an
  222.         incomplete test that results in everything with a zero
  223.         exponent to be printed as zero. Unfortunately, EXTENDED
  224.         denormals also have a zero exponent. The conversion routine
  225.         can be enhanced to correctly print out EXTENDED denormals
  226.         with very little additional code and only marginally increased
  227.         timing overhead for the normalized number conversion.
  228.  
  229.      4) The compiler does not allow to initialize typed constants
  230.         of types SINGLE, DOUBLE, and EXTENDED with a constant
  231.         representing a denormal. Without warning, zero is assigned
  232.         to these constants. For example the declaration
  233.  
  234.         CONST Foo: SINGLE = 1e-40;
  235.               Bar: DOUBLE = 1e-320;
  236.  
  237.         results in zero being stored to typed constants Foo and Bar.
  238.         This problem is probably caused by the use of the emulator
  239.         routines to compile programs with the $N+ flag. Since this
  240.         behavior is not documented in the manuals and no warning is
  241.         given during compilation, it is considered a bug.
  242.  
  243.      The following program tests the support for denormals and the
  244.      correct printing of denormals:
  245.  
  246.      {$N+,E+}
  247.  
  248.      PROGRAM DenormTst;
  249.  
  250.      VAR E: EXTENDED;
  251.          D: DOUBLE;
  252.          S: SINGLE;
  253.  
  254.      BEGIN
  255.         WriteLn ('Testing support and printing of denormals');
  256.         WriteLn;
  257.         Write ('Coprocessor is: ');
  258.         CASE Test8087 OF
  259.            0: WriteLn ('Emulator');
  260.            1: WriteLn ('8087 or compatible');
  261.            2: WriteLn ('80287 or compatible');
  262.            3: WriteLn ('80387 or compatible');
  263.         END;
  264.         WriteLn;
  265.         S := 1.18e-38;
  266.         S := S * 3.90625e-3;
  267.         IF S = 0 THEN
  268.            WriteLn ('SINGLE denormals not supported')
  269.         ELSE BEGIN
  270.            WriteLn ('SINGLE denormals supported');
  271.            WriteLn ('SINGLE denormal prints as:   ', S);
  272.            WriteLn ('Denormal should be printed as 4.60943...E-0041');
  273.         END;
  274.         WriteLn;
  275.         D := 2.24e-308;
  276.         D := D * 3.90625e-3;
  277.         IF D = 0 THEN
  278.            WriteLn ('DOUBLE denormals not supported')
  279.         ELSE BEGIN
  280.            WriteLn ('DOUBLE denormals supported');
  281.            WriteLn ('DOUBLE denormal prints as:   ', D);
  282.            WriteLn ('Denormal should be printed as 8.75...E-0311');
  283.         END;
  284.         WriteLn;
  285.         E := 3.37e-4932;
  286.         E := E * 3.90625e-3;
  287.         IF E = 0 THEN
  288.            WriteLn ('EXTENDED denormals not supported')
  289.         ELSE BEGIN
  290.            WriteLn ('EXTENDED denormals supported');
  291.            WriteLn ('EXTENDED denormal prints as: ', E);
  292.            WriteLn ('Denormal should be printed as 1.3164...E-4934');
  293.         END;
  294.      END.
  295.  
  296.      References:
  297.  
  298.      [1] IEEE Standard for Binary Floating-Point Arithmetic.
  299.          ANSI/IEEE Std 754-1985.
  300.          New York, NY: Institute of Electrical and Electronics Engineers
  301.          1985
  302.      [2] Goldberg, D.: Computer Arithmetic.
  303.          In: Hennessy, J.L; Patterson, D.A: Computer Architecture - A
  304.          Quantitative Approach. San Mateo, CA: Morgan Kaufmann 1990
  305.          Page A-27
  306.      [3] Intel: 387DX User's Manual. Programmers Reference. Intel 1989
  307.  
  308.  
  309.  
  310. 3. Error in EXTENDED to string conversion (Str, Write)
  311.  
  312.      There is an error in the internal conversion routine Float2Str
  313.      that converts an EXTENDED number to a string of decimal digits.
  314.      This bug causes some NANs to be printed as INFs. The code in
  315.      the routine fails to do a complete check on the mantissa to
  316.      figure out if it is an INF. It checks only the sixteen most
  317.      significant mantissa bits. Therefore, NANs with mantissa
  318.      between 800000000001h and 8000FFFFFFFFh are printed as INF.
  319.      The following program demonstrates the bug:
  320.  
  321.      {$N+,E+}
  322.      PROGRAM INFBug;
  323.  
  324.      VAR X:  EXTENDED;
  325.          XA: ARRAY [1..5] OF WORD ABSOLUTE X;
  326.  
  327.      BEGIN
  328.         WriteLn ('Testing correct printing of NANs');
  329.         XA [5] := $7FFF;
  330.         XA [4] := $8000;
  331.         XA [3] := $0000;
  332.         XA [2] := $0000;
  333.         XA [1] := $0001;
  334.         WriteLn ('First  NAN (7FFF 8000 0000 0000 0001) prints as: ', X);
  335.         XA [5] := $FFFF;
  336.         XA [4] := $8000;
  337.         XA [3] := $0000;
  338.         XA [2] := $8000;
  339.         XA [1] := $0000;
  340.         WriteLn ('Second NAN (FFFF 8000 0000 8000 0000) prints as: ', X);
  341.         XA [5] := $7FFF;
  342.         XA [4] := $8000;
  343.         XA [3] := $4000;
  344.         XA [2] := $0000;
  345.         XA [1] := $0000;
  346.         WriteLn ('Third  NAN (7FFF 8000 4000 0000 0000) prints as: ', X);
  347.      END.
  348.  
  349.  
  350.  
  351.      The following is an excerpt from the Float2Str routine showing the
  352.      faulty code that causes the bug:
  353.  
  354.      CMP     AX,7FFFH          ; INF or NAN ? (check exponent)
  355.      JNE     @@10              ; no, Normal
  356.      CMP     Value.w6,8000H    ; INF ?         <----- incomplete check !
  357.      JE      @@3               ; yes, print INF
  358.      MOV     AX,'AN'           ; no, print NAN
  359.      STOSW
  360.      MOV     AL,'N'
  361.      STOSB
  362.  
  363.      This fragment should be replaced by the following code which
  364.      eliminates the bug:
  365.  
  366.      CMP     AX,7FFFH          ; NAN or INF ? (check exponent)
  367.      JNE     @@10              ; no, Normal
  368.      MOV     DX, Value.w0      ; if any of
  369.      OR      DX, Value.w2      ;  last 48 mantissa bits
  370.      OR      DX, Value.w4      ;   is not zero,
  371.      JNZ     @3a               ;    must be NAN
  372.      CMP     Value.w6, 8000H   ; INF ?
  373.      JE      @@3               ; yes, print INF
  374.      @3a:
  375.      MOV     AX,'AN'           ; no, print NAN
  376.      STOSW
  377.      MOV     AL,'N'
  378.      STOSB
  379.  
  380.  
  381.  
  382. 4. Bug in string -> LONGINT conversion
  383.  
  384.      The VAL and READ procedures for LONGINTs do not allow the smallest
  385.      LONGINT number -2147483648 (-2^31) to be read in decimal form. It
  386.      can be entered in hexadecimal form $80000000 though. VAL and READ
  387.      should be changed to allow all valid LONGINTs to be read, especially
  388.      since this would not slow down the conversion process if done properly.
  389.  
  390.  
  391.  
  392. 5. Bug in Random function for $N+ state
  393.  
  394.      The Random function in programs compiled with $N+ can return the
  395.      number 1, although Random is specified to deliver values strictly
  396.      smaller than 1. This error occurs since the unsigned 32-bit integer
  397.      delivered by the random number generator is read into the coprocessor
  398.      as a signed 32-bit integer. To avoid negative numbers, the absolute
  399.      value is taken after that before the number is divided by 2^31. If
  400.      however, the 32-bit integer delivered by the random number generator
  401.      is 80000000h it will be converted to 2^31 by taking the absolute
  402.      value in the coprocessor. Division by 2^31 will then return 1. The
  403.      Random routine should be changed to read the 32-bit integers in
  404.      a 64-bit format, thus avoiding the negative number problem and the
  405.      FABS.
  406.  
  407.  
  408.  
  409. 6. Differences in compile-time and run-time evaluation of certain functions
  410.  
  411.      It seems that at least the Round function behaves differently at
  412.      compile time as compared to run time evaluation as demonstrated
  413.      by the following program:
  414.  
  415.      {$N+,E+}
  416.      PROGRAM RoundBug;
  417.  
  418.      CONST Y = 4.5;
  419.            J = Round (Y);
  420.  
  421.      VAR   I: INTEGER;
  422.            X: EXTENDED;
  423.  
  424.      BEGIN
  425.         X := Y;
  426.         I := Round (X);
  427.         WriteLn (I:5, J:5);
  428.      END.
  429.  
  430.      One would expect I and J to be equal in the output, but actual
  431.      output is:
  432.  
  433.         4    5
  434.  
  435.      The problem here is that the run-time version of Round uses the
  436.      Coprocessor/Emulator which provides correct IEEE-754 rounding to
  437.      nearest or even, while the compile time version of Round uses the
  438.      REAL software arithmetic with simple round to nearest or up. The
  439.      problem can easily be solved by implementing correct IEEE style
  440.      rounding for REAL arithmetic, which is strongly recommended
  441.      regardless of the bug given above.
  442.  
  443.  
  444.  
  445. 7. Documentation enhancement needed with regard to Sin/Cos functions
  446.  
  447.      When in $N+ mode, a call to the Sin and Cos functions with an
  448.      argument whose absolute value is > 2^63 = 9.22e18 will result
  449.      in an error. This makes sense, since a total loss of precision
  450.      will occur outside this range. However, the current version of
  451.      the documentation does not document this type of error.
  452.      In addition, the REAL type software arithmetic ($N-) will return
  453.      zero for the sine and cosine of large arguments. It does not
  454.      raise an error. When doing REAL computations in $N+ mode, an
  455.      error occurs in these cases. This difference in REAL arithmetic
  456.      between $N+ and $N- mode should be explained in the TP 6.0 manuals.
  457.  
  458.  
  459.  
  460. 8. Errors in REAL type software arithmetic
  461.  
  462.      There is an error in the REAL-Add/Subtract routine of TP6.0
  463.      runtime library, that may cause results to be less accurate
  464.      than would be possible. Before shifting the smaller operand's
  465.      mantissa to the right for alignment prior to mantissa addition,
  466.      a test is performed wether the shift would be for more than
  467.      39 bits. It is assumed that any shift count >= 40 would make
  468.      the second operand so small that it cannot affect the result.
  469.      This was true as long Turbo-Pascal truncated results in REAL
  470.      arithmetic, which was the case up to and including version 5.0
  471.      of the compiler. Due to the rounding introduced with TP 5.5
  472.      the above assumption no longer holds. Because of rounding,
  473.      which takes place at the 41st mantissa bit, a carry may pro-
  474.      pagate to the significant 40th bit of the final result. Therefore,
  475.      only when the shift counts needed for alignment is greater or
  476.      equal to 41 should the mantissa addition be skipped.
  477.  
  478.      There are constant errors in the REAL-Exp and REAL-Ln routines
  479.      that cause some uncessary inaccuracies in the function results.
  480.      The constant Sqrt(2) in EXP should be coded as 81 FA 33 F3 04 35,
  481.      not as 81 FB 33 F3 04 35, since the mantissa to six bytes of
  482.      accuracy would be 0.3504F333F9DE. Likewise, the constant 0.5*Sqrt(2)
  483.      used by LN should be 80 FA 33 F3 04 35.
  484.  
  485.      The REAL-Exp function returns with a runtime error 205 (overflow),
  486.      when called with an argument smaller than about -88.029. However,
  487.      even an argument of -88.72 would still deliver a result bigger than
  488.      the smallest normalized REAL number, which is 2^-128 or 2.94e-39.
  489.      Thus, Exp does not make use of the available argument range. Exp
  490.      should not abort with an error when called with very small arguments
  491.      anyhow. Since the exponential function approaches zero as the argument
  492.      approaches negative infinity, it should simply return zero when the
  493.      result is too small to be represented as a normalized real number.
  494.      This behavior of Exp would be consistent with the rest of the REAL
  495.      operations, since REAL arithmetic always takes the "flush to zero"
  496.      approach when results underflow.
  497.  
  498.  
  499.  
  500. 9. Error in REAL-type multiplication
  501.  
  502.      The multiplication routine for REAL type software arithmetic
  503.      provides a faster multiplication if all but the first sixteen
  504.      bits of the mantissa in one of the factors is zero. This makes
  505.      multiplication much faster when a floating point number is to
  506.      be multiplied by a small integer, such as in 3.1415926*10, since
  507.      the converted integer does not use more than the first sixteen
  508.      mantissa bits.
  509.      This part of the multiplication contains a logical error. The
  510.      bug will introduce a relative error of at most 3e-12 in the
  511.      result, whereas all other basic arithmetical operation (with
  512.      the exception of addition, see above) are accurate to the
  513.      theoretical limit of the arithmetic (9.09e-13). The bug causes
  514.      incorrect results in about 3% of the described type of multi-
  515.      plications. The errors is caused by not using all necessary
  516.      mantissa bits in the computation. The code looks as follows:
  517.      .
  518.      .
  519.      8BC5    MOV   AX, BP   ; the partial product
  520.      8AC4    MOV   AL, AH   ; of CH * (LSB of BP) is
  521.      F6E5    MUL   CH       ; not taken into consideration
  522.      8BD8    MOV   BX, AX   ; by this computation
  523.      .
  524.  
  525.      The bug can be eliminated by applying the following patch:
  526.  
  527.  
  528.      89C8    MOV   AX, CX   ; this code performs the correct
  529.      F7E5    MUL   BP       ; computation. Note that the
  530.      89D3    MOV   BX, DX   ; correct value stored in BX
  531.      90      NOP            ; may exceed the corresponding
  532.      90      NOP            ; value above by up to three
  533.  
  534.  
  535.  
  536. 10. Incorrectly restricted argument range for REAL arithmetic Round/Trunc
  537.  
  538.      When using REAL arithmetic ($N- mode), the Round/Trunc functions
  539.      raise an error for all inputs that would cause the smallest
  540.      LONGINT number -2147483648 to be returned. Thus inputs to Round
  541.      are restricted to -2147483647.5 < x < 2147483647.5, although the
  542.      correct available argument range should be -2147483648.5 <= x <
  543.      2147483647.5 . Inputs to Trunc are similarly incorrectly restricted
  544.      to -2147483648 < x < 2147483648 where the correct range would be
  545.      -2147483649 < x < 2147483648. This bug can be corrected with no
  546.      increase in execution time. Correct implementation of Round/Trunc
  547.      can be tested with the ROUNDTST program given below. Try a run
  548.      with {$N+} and then try it with {$N-}.
  549.  
  550.  
  551.      PROGRAM RoundTst;
  552.  
  553.      VAR X,Y,Z: REAL;
  554.          I:     LONGINT;
  555.  
  556.      BEGIN
  557.         Y := 4.5;
  558.         Z := 5.5;
  559.         WriteLn ('Testing implementation of Round/Trunc for correct range',
  560.                  ' and IEEE-rounding');
  561.         WriteLn;
  562.         WriteLn;
  563.         Write   ('Testing range of Round towards lower limit ... ');
  564.         X := -2147483647.0;
  565.         REPEAT
  566.            I := Round (X);
  567.      (*    WriteLn (X+2147483648.0);*)
  568.            X := X - 1.0/256.0;
  569.         UNTIL X < -2147483648.5;
  570.         WriteLn ('passed');
  571.         WriteLn;
  572.         Write   ('Testing range of Round towards upper limit ... ');
  573.         X := 2147483647.0;
  574.         REPEAT
  575.            I := Round (X);
  576.      (*    writeln (x-2147483648.0);*)
  577.            X := X + 1.0/256.0;
  578.         UNTIL X >= 2147483647.5;
  579.         WriteLn ('passed');
  580.         WriteLn;
  581.         Write   ('Testing range of Trunc towards lower limit ... ');
  582.         X := -2147483647.0;
  583.         REPEAT
  584.            I := Trunc (X);
  585.      (*    writeln (x+2147483648.0);*)
  586.            X := X - 1.0/256.0;
  587.         UNTIL X <= -2147483649.0;
  588.         WriteLn ('passed');
  589.         WriteLn;
  590.         Write   ('Testing range of Trunc towards upper limit ... ');
  591.         X := 2147483647.0;
  592.         REPEAT
  593.            I := Trunc (X);
  594.      (*    writeln (x-2147483648.0);*)
  595.            X := X + 1.0/256.0;
  596.         UNTIL X >= 2147483648.0;
  597.         WriteLn ('passed');
  598.         WriteLn;
  599.         Write   ('Round (4.5) should be: 4, actual value is: ', Round (Y));
  600.         IF Round (Y) = 4 THEN
  601.            WriteLn ('   passed')
  602.         ELSE
  603.            WriteLn ('   failed');
  604.         Write   ('Round (5.5) should be: 6, actual value is: ', Round (Z));
  605.         IF Round (Z) = 6 THEN
  606.            WriteLn ('   passed')
  607.         ELSE
  608.            WriteLn ('   failed');
  609.         WriteLn;
  610.         Y := -4.5;
  611.         Z := -5.5;
  612.         Write   ('Round (-4.5) should be:-4, actual value is:', Round (Y));
  613.         IF Round (Y) =-4 THEN
  614.            WriteLn ('  passed')
  615.         ELSE
  616.            WriteLn ('  failed');
  617.         Write   ('Round (-5.5) should be:-6, actual value is:', Round (Z));
  618.         IF Round (Z) =-6 THEN
  619.            WriteLn ('  passed')
  620.         ELSE
  621.            WriteLn ('  failed');
  622.      END.
  623.  
  624.  
  625.  
  626. 11. Errors in coprocessor emulator
  627.  
  628.      Certain coprocessor instructions will not be correctly emulated
  629.      by the emulation package of Turbo-Pascal 6.0. This bug has also
  630.      been present in all previous versions of the emulator. The
  631.      following program will demonstrate the faulty emulation of
  632.      FDECSTP:
  633.  
  634.      {$A+,B-,D+,E+,F-,G-,I-,L+,N+,O-,R-,S-,V+,X-}
  635.      {$M 16384,0,655360}
  636.      PROGRAM EMUBUG;
  637.  
  638.      VAR StackPointer:        BYTE;
  639.          Control87, Status87: WORD;
  640.  
  641.      BEGIN
  642.        WriteLn ('Turbo Pascal 6.0 coprocessor emulator bug demo program');
  643.        WriteLn;
  644.        IF Test8087 <> 0 THEN
  645.           WriteLn ('Initializing coprocessor')
  646.        ELSE
  647.           WriteLn ('Initializing emulator');
  648.        WriteLn ('Loading Ï€, 1, and 0 into coprocessor / emulator');
  649.        ASM
  650.           FSTCW   [Control87]       { save control word as set by Turbo Pascal}
  651.           FINIT                     { initialize coprocessor / emulator }
  652.           FLDPI                     { load Ï€, stack pointer = 7 }
  653.           FLD1                      { load 1, stack pointer = 6 }
  654.           FLDZ                      { load 0, stack pointer = 5 }
  655.           FSTSW   [Status87]        { save status word containing stack pointer}
  656.           FWAIT                     { wait until saved }
  657.           MOV     AX, [Status87]    { load status word }
  658.           AND     AH, 38h           { extract stack pointer field }
  659.           SHR     AH, 1             { make }
  660.           SHR     AH, 1             {  it  }
  661.           SHR     AH, 1             {   right-aligned in byte }
  662.           MOV     [StackPointer], AH{ store stack pointer value }
  663.        END;
  664.        IF Test8087 = 0 THEN
  665.           WriteLn ('emulator stack pointer now: ', StackPointer,
  666.                    '  should be: 5')
  667.        ELSE
  668.           WriteLn ('coprocessor stack pointer now: ', StackPointer,
  669.                    '  should be: 5');
  670.        WriteLn ('executing / emulating FDECSTP instruction');
  671.        ASM
  672.           FDECSTP                   { decrement coprocessor/emulator stack ptr }
  673.           FSTSW   [Status87]        { store status word containing stack ptr }
  674.           FWAIT                     { wait until stored }
  675.           MOV     AH, BYTE PTR [Status87+1] { get status word MSB }
  676.           AND     AH, 38h           { isolate stack ptr field in status word }
  677.           SHR     AH, 1             { make              }
  678.           SHR     AH, 1             {  it left          }
  679.           SHR     AH, 1             {   aligned in byte }
  680.           MOV     [StackPointer], AH{ store stack pointer value }
  681.        END;
  682.        IF Test8087 = 0 THEN
  683.           WriteLn ('emulator stack pointer now: ', StackPointer,
  684.                    '  should be: 4')
  685.        ELSE
  686.           WriteLn ('coprocessor stack pointer now: ', StackPointer,
  687.                    '  should be: 4');
  688.        ASM
  689.           FINIT                     { initalize coprocessor/emulator }
  690.           FLDCW   [Control87]       { restore TURBO Pascal control word }
  691.        END;
  692.      END.
  693.  
  694.  
  695.      When the above program is run with a coprocessor, the results will
  696.      be as expected:
  697.  
  698.  
  699.      Turbo Pascal 6.0 coprocessor emulator bug demo program
  700.  
  701.      Initializing coprocessor
  702.      Loading Ï€, 1, and 0 into coprocessor / emulator
  703.      coprocessor stack pointer now: 5  should be: 5
  704.      executing / emulating FDECSTP instruction
  705.      coprocessor stack pointer now: 4  should be: 4
  706.  
  707.  
  708.  
  709.      However, when run with the emulator, strange things happen:
  710.  
  711.  
  712.      Turbo Pascal 6.0 coprocessor emulator bug demo program
  713.  
  714.      Initializing emulator
  715.      Loading Ï€, 1, and 0 into coprocessor / emulator
  716.      emulator stack pointer now: 5  should be: 5
  717.      executing / emulating FDECSTP instruction
  718.      emulator stack pointer now: 6  should be: 4
  719.  
  720.  
  721.      It seems that at least FINCSTP and FDECSTP are incorrectly emu-
  722.      lated. Tests show that FINCSTP actually decreases the stack pointer,
  723.      while FDECSTP increases it. This is caused by faulty entries in
  724.      the jump index table for emulated opcodes D9E0 to D9FF. Exchanging
  725.      the indices for FINCSTP and FDECSTP will cause FINCSTP to function
  726.      correctly, but because of another error FDECSTP will disturb the
  727.      emulated registers of the 80x87 emulator, which it shouldn't do.
  728.      FINCSTP and FDECSTP instructions will not be generated by the
  729.      compiler. However, programs that link with modules written in
  730.      assembly language or use the new ASM directive of Turbo-Pascal 6.0
  731.      might contain them. When run with the emulator, these programs
  732.      will behave odd or might even crash. In addition, the wraparound
  733.      stack addressing provided by the coprocessor is unavailable on the
  734.      emulator. On the coprocessor, an instruction such as FADD ST(7),ST
  735.      would write to register #2 if the current stacktop was three. The
  736.      emulator computes a linear offset and tries to write to a non-
  737.      implemented register #10. In doing so, it destroys other emulator
  738.      data residing at offsets C0h to E5h in the stack segment, just
  739.      above the emulator's register file (60h to BFh). This will cause
  740.      the emulator to malfunction or will crash the program.
  741.  
  742.      The best way to get rid of this bugs would be to fix the emulator
  743.      so that it correctly emulates all coprocessor instructions and the
  744.      wraparound addressing. This should prove not to be too difficult,
  745.      since the faulty instructions are among the easiest to emulate. When
  746.      figuring out which register is meant in stack top relativ addressing,
  747.      the result should be taken modulo 8 to provide correct wraparound.
  748.      This should be quite easy. Additional code needed will be at a mini-
  749.      mum.
  750.      The other solution would be to trap all unemulated or faulty
  751.      instructions (e.g. FINCSTP, FADD ST(7),ST) upon invocation of the
  752.      emulator. The emulator would then emit an error message such as
  753.      'run-time error xx, unemulated coprocessor instruction' and abort
  754.      the program. In this case, the documentation should provide explicit
  755.      information which instructions are not emulated and should not be
  756.      used if the program is to perform correctly with the emulator. Also,
  757.      all other differences between coprocessor and emulator should be
  758.      explained.
  759.  
  760.  
  761.  
  762. 12. Deficiencies of coprocessor emulator
  763.  
  764.      The emulator of Turbo Pascal 6.0 does not emulate the following
  765.      features of a physical coprocessor: precision control and rounding
  766.      control. This can be proved by running the following programs
  767.      with and without a coprocessor.
  768.  
  769.      {$N+,E+}
  770.      PROGRAM PCtrlTst;
  771.  
  772.      VAR B: EXTENDED;
  773.          Precision, L: WORD;
  774.  
  775.      PROCEDURE SetPrecisionControl (Precision: WORD);
  776.      (* This procedure sets the internal precision of the NDP. Available *)
  777.      (* precision values:  0  -  24 bits (SINGLE)                        *)
  778.      (*                    1  -  n.a. (mapped to single)                 *)
  779.      (*                    2  -  53 bits (DOUBLE)                        *)
  780.      (*                    3  -  64 bits (EXTENDED)                      *)
  781.  
  782.      VAR CtrlWord: WORD;
  783.  
  784.      BEGIN {SetPrecisionCtrl}
  785.         IF Precision = 1 THEN
  786.            Precision := 0;
  787.         Precision := Precision SHL 8; { make mask for PC field in ctrl word }
  788.         ASM
  789.            FSTCW    [CtrlWord]        { store NDP control word }
  790.            MOV      AX, [CtrlWord]    { load control word into CPU }
  791.            AND      AX, 0FCFFh        { mask out precision control field }
  792.            OR       AX, [Precision]   { set desired precision in PC field }
  793.            MOV      [CtrlWord], AX    { store new control word }
  794.            FLDCW    [CtrlWord]        { set new precision control in NDP }
  795.         END;
  796.      END; {SetPrecisionCtrl}
  797.  
  798.      BEGIN {main}
  799.         FOR Precision := 1 TO 3 DO BEGIN
  800.            B := 1.2345678901234567890;
  801.            SetPrecisionControl (Precision);
  802.            FOR L := 1 TO 20 DO BEGIN
  803.               B := Sqrt (B);
  804.            END;
  805.            FOR L := 1 TO 20 DO BEGIN
  806.               B := B*B;
  807.            END;
  808.            SetPrecisionControl (3);   { full precision for printout }
  809.            WriteLn (Precision, B:28);
  810.         END;
  811.      END.
  812.  
  813.  
  814.      The output of the above program looks like this when executed
  815.      with a coprocessor present:
  816.  
  817.      1   1.13311278820037842E+0000        (* single precision   *)
  818.      2   1.23456789006442125E+0000        (* double precision   *)
  819.      3   1.23456789012337585E+0000        (* extended precision *)
  820.  
  821.      However, when executed with the emulator, output is as follows:
  822.  
  823.      1   1.23456789012351396E+0000
  824.      2   1.23456789012351396E+0000
  825.      3   1.23456789012351396E+0000
  826.  
  827.      Changing the value of precision control obviously has no effect at
  828.      all on the emulator. It always works with extended precision in
  829.      internal calculations. This deviation of the emulator from a real
  830.      coprocessor should be documented in the TP 6.0 User Manual.
  831.  
  832.      {$N+,E+}
  833.      PROGRAM RCtrlTst;
  834.  
  835.      VAR B: EXTENDED;
  836.          RoundingMode, L: WORD;
  837.  
  838.  
  839.      PROCEDURE SetRoundingMode (RCMode: WORD);
  840.      (* This procedure selects one of four available rounding modes *)
  841.      (* 0  -  Round to nearest (default)                            *)
  842.      (* 1  -  Round down (towards negative infinity)                *)
  843.      (* 2  -  Round up (towards positive infinity)                  *)
  844.      (* 3  -  Chop (truncate, round towards zero)                   *)
  845.  
  846.      VAR CtrlWord: WORD;
  847.  
  848.      BEGIN
  849.         RCMode := RCMode SHL 10;      { make mask for RC field in control word}
  850.         ASM
  851.            FSTCW    [CtrlWord]        { store NDP control word }
  852.            MOV      AX, [CtrlWord]    { load control word into CPU }
  853.            AND      AX, 0F3FFh        { mask out rounding control field }
  854.            OR       AX, [RCMode]      { set desired precision in RC field }
  855.            MOV      [CtrlWord], AX    { store new control word }
  856.            FLDCW    [CtrlWord]        { set new rounding control in NDP }
  857.         END;
  858.      END;
  859.  
  860.      BEGIN
  861.         FOR RoundingMode := 0 TO 3 DO BEGIN
  862.            B := 1.2345678901234567890e100;
  863.            SetRoundingMode (RoundingMode);
  864.            FOR L := 1 TO 51 DO BEGIN
  865.               B := Sqrt (B);
  866.            END;
  867.               FOR L := 1 TO 51 DO BEGIN
  868.               B := -B*B;
  869.            END;
  870.            SetRoundingMode (0);        { round to nearest for printout }
  871.            WriteLn (RoundingMode, B:28);
  872.         END;
  873.      END.
  874.  
  875.  
  876.      The calculations performed in the above program were selected so
  877.      that every rounding mode would lead to a distinct final value. The
  878.      output when run with a coprocessor appears below.
  879.  
  880.      As expected, four different values are printed at the end of the
  881.      program if a coprocessor is present.
  882.  
  883.      0  -1.23427629010100635E+0100         (* round nearest *)
  884.      1  -1.23427623555772409E+0100         (* round down    *)
  885.      2  -1.23457760966801097E+0100         (* round up      *)
  886.      3  -1.23397493540770643E+0100         (* chop          *)
  887.  
  888.      With the emulator, four identical results are produced, indicating
  889.      that the emulator does not support the IEEE rounding modes of the
  890.      coprocessor.
  891.  
  892.      0  -1.23457766383395931E+0100
  893.      1  -1.23457766383395931E+0100
  894.      2  -1.23457766383395931E+0100
  895.      3  -1.23457766383395931E+0100
  896.  
  897.      This deviation from the behavior of the actual coprocessor should
  898.      be mentioned in TP 6.0 documentation.
  899.  
  900.  
  901.  
  902. 13. Deficiencies in Coprocessor emulator
  903.  
  904.      The coprocessor emulator used by programs compiled in the
  905.      $N+,E+ mode when a coprocessor is absent at run-time does
  906.      not correctly handle special arguments like ZERO, INF, and
  907.      NANs. Specific problems are:
  908.  
  909.      - multiplication and division with INFs resulting in NANs
  910.        instead of INFs
  911.      - 0+(-0) = -0, but (-0)+0 = 0
  912.      - operations on QNANs (quiet NaNs) signaling an exception
  913.  
  914.      The following program will demonstrate the bugs:
  915.  
  916.  
  917.      {$N+,E+}
  918.      PROGRAM InfTest;
  919.  
  920.      VAR INF, NEGINF: EXTENDED;
  921.          QNAN, SNAN:  EXTENDED;
  922.          X, NEGX:     EXTENDED;
  923.          Z, NEGZ:     EXTENDED;
  924.          PSEUDOZERO:  EXTENDED;
  925.  
  926.          INFA: ARRAY [1..5] OF WORD ABSOLUTE INF;
  927.          QA:   ARRAY [1..5] OF WORD ABSOLUTE QNAN;
  928.          SA:   ARRAY [1..5] OF WORD ABSOLUTE SNAN;
  929.          PA:   ARRAY [1..5] OF WORD ABSOLUTE PSEUDOZERO;
  930.  
  931.      BEGIN
  932.         INFA [5] := $7FFF;
  933.         INFA [4] := $8000;
  934.         INFA [3] := $0000;
  935.         INFA [2] := $0000;
  936.         INFA [1] := $0000;
  937.         QA [5]   := $7FFF;
  938.         QA [4]   := $C000;
  939.         QA [3]   := $0000;
  940.         QA [2]   := $0000;
  941.         QA [1]   := $0001;
  942.         SA [5]   := $7FFF;
  943.         SA [4]   := $8000;
  944.         SA [3]   := $0000;
  945.         SA [2]   := $0000;
  946.         SA [1]   := $0001;
  947.         PA [5]   := $52FB;
  948.         PA [4]   := $0000;
  949.         PA [3]   := $0000;
  950.         PA [2]   := $0000;
  951.         PA [1]   := $0000;
  952.         NEGINF := -INF;
  953.         X := 5;
  954.         NEGX := -5;
  955.         Z := 0;
  956.         NEGZ := -Z;
  957.  
  958.         WriteLn (' INF +  INF: ', INF + INF:6:0,       '   should be  INF');
  959.         WriteLn ('-INF + -INF: ', NEGINF + NEGINF:6:0, '   should be -INF');
  960.         WriteLn (' INF +   X : ', INF + X:6:0,         '   should be  INF');
  961.  
  962.         WriteLn (' INF - -INF: ', INF - NEGINF:6:0,    '   should be  INF');
  963.         WriteLn ('-INF -  INF: ', NEGINF - INF:6:0,    '   should be -INF');
  964.         WriteLn ('  X  -  INF: ', X - INF:6:0,         '   should be -INF');
  965.  
  966.         WriteLn (' INF *  INF: ', INF * INF:6:0,       '   should be  INF');
  967.         WriteLn ('-INF *  INF: ', NEGINF * INF:6:0,    '   should be -INF');
  968.         WriteLn (' INF * -INF: ', INF * NEGINF:6:0,    '   should be -INF');
  969.         WriteLn ('-INF * -INF: ', NEGINF * NEGINF:6:0, '   should be  INF');
  970.         WriteLn ('  X  *  INF: ', X * INF:6:0,         '   should be  INF');
  971.         WriteLn (' -X  *  INF: ', NEGX * INF:6:0,      '   should be -INF');
  972.  
  973.         WriteLn (' INF /   0 : ', INF / 0:6:0,         '   should be  INF');
  974.         WriteLn ('-INF /   0 : ', NEGINF / 0:6:0,      '   should be -INF');
  975.         WriteLn ('  X  /  INF: ', X / INF:6:0,         '   should be   0');
  976.         WriteLn (' INF /  -X : ', INF / NEGX:6:0,      '   should be  INF');
  977.  
  978.         WriteLn (' Sqrt (INF): ', Sqrt (INF):6:0,      '   should be  INF');
  979.  
  980.         WriteLn (' -0  +  -0 : ', NEGZ + NEGZ:6:0,     '   should be  -0');
  981.         WriteLn ('  0  +  -0 : ', Z + NEGZ:6:0,        '   should be   0');
  982.         WriteLn (' -0  +   0 : ', NEGZ + Z:6:0,        '   should be   0');
  983.         WriteLn (' -0  *   0 : ', NEGZ * Z:6:0,        '   should be  -0');
  984.         WriteLn ('  0  *  -0 : ', Z * NEGZ:6:0,        '   should be  -0');
  985.         WriteLn (' -0  *   X : ', NEGZ * X:6:0,        '   should be  -0');
  986.         WriteLn ('  X  *  -0 : ', X * NEGZ:6:0,        '   should be  -0');
  987.         WriteLn (' -X  *   0 : ', NEGX * Z:6:0,        '   should be  -0');
  988.         WriteLn (' -X  *  -0 : ', NEGX * NEGZ:6:0,     '   should be   0');
  989.         WriteLn (' Sqrt (-0) : ', Sqrt (NEGZ):6:0,     '   should be  -0');
  990.  
  991.         WriteLn ('QNAN * QNAN: ', QNAN * QNAN:6:0,     '   should be  NAN');
  992.         WriteLn ('QNAN + QNAN: ', QNAN + QNAN:6:0,     '   should be  NAN');
  993.         WriteLn ('QNAN / QNAN: ', QNAN / QNAN:6:0,     '   should be  NAN');
  994.  
  995.         WriteLn ('Sqrt (QNAN): ', Sqrt (QNAN):6:0,     '   should be  NAN');
  996.  
  997.      END. { InfTest}
  998.  
  999.  
  1000.      When run on an 80387 coprocessor, the output of the above program
  1001.      is as follows:
  1002.  
  1003.       INF +  INF:    INF   should be  INF
  1004.      -INF + -INF:   -INF   should be -INF
  1005.       INF +   X :    INF   should be  INF
  1006.       INF - -INF:    INF   should be  INF
  1007.      -INF -  INF:   -INF   should be -INF
  1008.        X  -  INF:   -INF   should be -INF
  1009.       INF *  INF:    INF   should be  INF
  1010.      -INF *  INF:   -INF   should be -INF
  1011.       INF * -INF:   -INF   should be -INF
  1012.      -INF * -INF:    INF   should be  INF
  1013.        X  *  INF:    INF   should be  INF
  1014.       -X  *  INF:   -INF   should be -INF
  1015.       INF /   0 :    INF   should be  INF
  1016.      -INF /   0 :   -INF   should be -INF
  1017.        X  /  INF:      0   should be   0
  1018.       INF /  -X :   -INF   should be  INF
  1019.       Sqrt (INF):    INF   should be  INF
  1020.       -0  +  -0 :     -0   should be  -0
  1021.        0  +  -0 :      0   should be   0
  1022.       -0  +   0 :      0   should be   0
  1023.       -0  *   0 :     -0   should be  -0
  1024.        0  *  -0 :     -0   should be  -0
  1025.       -0  *   X :     -0   should be  -0
  1026.        X  *  -0 :     -0   should be  -0
  1027.       -X  *   0 :     -0   should be  -0
  1028.       -X  *  -0 :      0   should be   0
  1029.       Sqrt (-0) :     -0   should be  -0
  1030.      QNAN * QNAN:    NAN   should be  NAN
  1031.      QNAN + QNAN:    NAN   should be  NAN
  1032.      QNAN / QNAN:    NAN   should be  NAN
  1033.      Sqrt (QNAN):    NAN   should be  NAN
  1034.  
  1035.  
  1036.      However, when run with the emulator, the programs output looks
  1037.      like this:
  1038.  
  1039.       INF +  INF:    INF   should be  INF
  1040.      -INF + -INF:   -INF   should be -INF
  1041.       INF +   X :    INF   should be  INF
  1042.       INF - -INF:    INF   should be  INF
  1043.      -INF -  INF:   -INF   should be -INF
  1044.        X  -  INF:   -INF   should be -INF
  1045.       INF *  INF:    NAN   should be  INF         <----- error
  1046.      -INF *  INF:    NAN   should be -INF         <----- error
  1047.       INF * -INF:    NAN   should be -INF         <----- error
  1048.      -INF * -INF:    NAN   should be  INF         <----- error
  1049.        X  *  INF:    NAN   should be  INF         <----- error
  1050.       -X  *  INF:    NAN   should be -INF         <----- error
  1051.       INF /   0 :    NAN   should be  INF         <----- error
  1052.      -INF /   0 :    NAN   should be -INF         <----- error
  1053.        X  /  INF:      0   should be   0
  1054.       INF /  -X :    NAN   should be  INF         <----- error
  1055.       Sqrt (INF):    INF   should be  INF
  1056.       -0  +  -0 :     -0   should be  -0
  1057.        0  +  -0 :     -0   should be   0          <----- error
  1058.       -0  +   0 :      0   should be   0
  1059.       -0  *   0 :     -0   should be  -0
  1060.        0  *  -0 :     -0   should be  -0
  1061.       -0  *   X :     -0   should be  -0
  1062.        X  *  -0 :     -0   should be  -0
  1063.       -X  *   0 :     -0   should be  -0
  1064.       -X  *  -0 :      0   should be   0
  1065.       Sqrt (-0) :     -0   should be  -0
  1066.      QNAN * QNAN: Runtime error 207 at 0000:09F8. <----- error
  1067.  
  1068.  
  1069.      This handling of QNANs also violates the IEEE-754 standard for
  1070.      binary floating point arithmetic, which states in section
  1071.      6.2 (Operations with NaNs): "Every operation involving one
  1072.      or two input NaNs, none of them signaling, shall signal no
  1073.      exception but, if a floating-point result is to be delivered,
  1074.      shall deliver as its result a quiet NaN, which should be one
  1075.      of the input NaNs".
  1076.  
  1077.  
  1078.  
  1079.  
  1080. 14. Error in inline assembler
  1081.  
  1082.      The inline assembler incorrectly accepts a type name as a variable
  1083.      name for memory operands, if the type size is the same as the size
  1084.      of the memory operand. The following program fragment is legal in
  1085.      the current version of the inline assembler:
  1086.  
  1087.      ASM
  1088.        ...
  1089.        MOV   AX, [WORD]
  1090.        MOV   AL, [BYTE]
  1091.        LES   DI, [LONGINT]
  1092.        ...
  1093.      END;
  1094.  
  1095.      The address generated for the memory operands in these cases is
  1096.      always 0. This bug should be fixed immediately.
  1097.  
  1098.  
  1099.  
  1100. 15. Error in inline assembler
  1101.  
  1102.      The PTR operator of the inline assembler will incorrectly accept
  1103.      an expression of class register if the size of the register is
  1104.      the same as the type it is casted to. The following statements are
  1105.      legal in the current version of the inline assembler:
  1106.  
  1107.      ASM
  1108.      ...
  1109.         MOV   BYTE PTR AL, 5
  1110.         ADD   WORD PTR AX, 5
  1111.         MOV   WORD PTR ES, 6
  1112.      ...
  1113.      END;
  1114.  
  1115.      The assembler will generate memory references to memory location
  1116.      0 in these cases, e.g. MOV BYTE PTR [0], 5. The PTR operator
  1117.      should be fixed to only accept expressions of class memory.
  1118.  
  1119.  
  1120.  
  1121. 16. Possible problems arising out of the use of the SEG and OFFSET
  1122.     operators with local variables
  1123.  
  1124.      Use of the SEG operator with local variables and parameters is
  1125.      allowed in the inline assembler. One would expect that the SEG
  1126.      value of a local (auto) variable is the value of the stack
  1127.      segment (SS), just as the SEG value of a global (static) variable
  1128.      is the data segment (DS, @Data). However, the value stored by the
  1129.      inline assembler seems always to be 0. Applying the SEG operator
  1130.      to local variables should either be forbidden, or the correct
  1131.      value should be supplied by the assembler.
  1132.  
  1133.      The Programmer's Guide states that the OFFSET of local variables,
  1134.      parameters, and the @Result symbol is the offset relative to
  1135.      the framepointer of the entity in which they were declared. This
  1136.      works as expected, but there is a problem when nested procedures/
  1137.      functions are used. Although the local variables of an outer
  1138.      procedural level are visible to inner procedures, no meaningful
  1139.      OFFSET value can be computed for these variables relative to
  1140.      the framepointer of the inner procedure. Therefore, the assembler
  1141.      should either forbid references to local variables declared at an
  1142.      outer level or generate the code necessary to address across the
  1143.      several levels of indirection involved. The following example
  1144.      illustrates the problem:
  1145.  
  1146.  
  1147.      FUNCTION Outer (R: WORD): WORD; { copies input R to function output }
  1148.  
  1149.      FUNCTION Inner (W: WORD): WORD; ASSEMBLER; {should copy R to func. output}
  1150.      ASM
  1151.         MOV   AX, R                  { will not load R !!}
  1152.      END;
  1153.  
  1154.      BEGIN { Outer }
  1155.         R := Inner (R);              { does *not* copy R to R !! }
  1156.         ASM
  1157.            MOV   DI, OFFSET R        { load offset of R relative to Outer's BP}
  1158.            MOV   AX, [BP+DI]         { load parameter R }
  1159.            MOV   SI, OFFSET @Result  { load offset of Outer's result }
  1160.            MOV   [BP+SI], AX         { store value of R into Outer's result }
  1161.         END;
  1162.      END;
  1163.  
  1164.      BEGIN
  1165.         WriteLn (Outer(5));
  1166.      END.
  1167.  
  1168.      Instead of printing '5', as one would expect, this program prints a
  1169.      value like '9094' depending on the initial stack size set in the
  1170.      TP configuration.
  1171.  
  1172.  
  1173.  
  1174. 17. Error when using the WITH directive with the inline assembler
  1175.  
  1176.      When accessing parts of a record using inline assembler from within
  1177.      a WITH block, the inline assembler doesn't correctly compute the
  1178.      addresses of the record's parts. It uses the offset of the record
  1179.      part within the record as the address. However, the base address of
  1180.      the record must be added to this value to get the correct address
  1181.      of the record part. Writing to record parts using the inline assembler
  1182.      from within a WITH block will destroy other data in the data segment.
  1183.      The following program illustrates the problem. It first initializes
  1184.      a record using the inline assembler without making use of a WITH
  1185.      block. It prints the contents of the record, then updates it using
  1186.      inline assembler from within a WITH block and print the record again.
  1187.      If the inline assembler worked correctly, two different printouts
  1188.      would be the result. Actually, the second record update doesn't
  1189.      change the record but destroys other data in the data segment.
  1190.      Therefore, the same data is printed out twice.
  1191.  
  1192.  
  1193.      {$A+,N-,I-,S-,R-,B-}
  1194.  
  1195.      PROGRAM WITHBug;
  1196.  
  1197.      VAR Student: RECORD
  1198.                      ID:   LONGINT;
  1199.                      Name: STRING;
  1200.                      GPA:  REAL;
  1201.                   END;
  1202.  
  1203.      BEGIN
  1204.         { first student: ID = 12345678, Name = JOHN, GPA = 1.0 }
  1205.  
  1206.         ASM
  1207.            MOV WORD PTR [Student.ID], 5678
  1208.            MOV WORD PTR [Student.ID+2], 1234
  1209.            MOV WORD PTR [Student.GPA], 81h
  1210.            MOV WORD PTR [Student.GPA+2], 0
  1211.            MOV WORD PTR [Student.GPA+4], 0
  1212.            MOV BYTE PTR [Student.Name], 4
  1213.            MOV WORD PTR [Student.Name+1], 'OJ'
  1214.            MOV WORD PTR [Student.Name+3], 'NH'
  1215.         END;
  1216.         WriteLn ('Student''s ID:   ', Student.ID);
  1217.         WriteLn ('          Name: ', Student.Name);
  1218.         WriteLn ('          GPA:  ', Student.GPA:0:2);
  1219.  
  1220.         { second student: ID = 87654321, Name = JANE, GPA = 2.0 }
  1221.  
  1222.         WITH Student DO
  1223.            ASM
  1224.               MOV WORD PTR [ID], 4321
  1225.               MOV WORD PTR [ID+2], 8765
  1226.               MOV WORD PTR [GPA], 82h
  1227.               MOV WORD PTR [GPA+2], 0
  1228.               MOV WORD PTR [GPA+4], 0
  1229.               MOV BYTE PTR [Name], 4
  1230.               MOV WORD PTR [Name+1], 'AJ'
  1231.               MOV WORD PTR [Name+3], 'EN'
  1232.            END;
  1233.         WriteLn ('Student''s ID:   ', Student.ID);
  1234.         WriteLn ('          Name: ', Student.Name);
  1235.         WriteLn ('          GPA:  ', Student.GPA:0:2);
  1236.      END.
  1237.  
  1238.  
  1239.      The following excerpts from the resulting code show the error:
  1240.  
  1241.      1738:003B C70644002E16   MOV    Word Ptr [0044],162E  ; 1st record
  1242.      1738:0041 C7064600D204   MOV    Word Ptr [0046],04D2  ; initialization
  1243.      1738:0047 C70648018100   MOV    Word Ptr [0148],0081  ; using
  1244.      1738:004D C7064A010000   MOV    Word Ptr [014A],0000  ; correct
  1245.      1738:0053 C7064C010000   MOV    Word Ptr [014C],0000  ; addresses
  1246.      1738:0059 C606480004     MOV    Byte Ptr [0048],04
  1247.      1738:005E C70649004A4F   MOV    Word Ptr [0049],4F4A
  1248.      1738:0064 C7064B00484E   MOV    Word Ptr [004B],4E48
  1249.  
  1250.      1738:00E4 C7060000E110   MOV    Word Ptr [0000],10E1  ; 2nd record
  1251.      1738:00EA C70602003D22   MOV    Word Ptr [0002],223D  ; initialization
  1252.      1738:00F0 C70604018200   MOV    Word Ptr [0104],0082  ; using offsets
  1253.      1738:00F6 C70606010000   MOV    Word Ptr [0106],0000  ; into record
  1254.      1738:00FC C70608010000   MOV    Word Ptr [0108],0000  ; instead of
  1255.      1738:0102 C606040004     MOV    Byte Ptr [0004],04    ; addresses
  1256.      1738:0107 C70605004A41   MOV    Word Ptr [0005],414A
  1257.      1738:010D C70607004E45   MOV    Word Ptr [0007],454E
  1258.  
  1259.  
  1260.  
  1261. 18. Incorrect assembly of certain JMPs and CALLs by inline assembler
  1262.  
  1263.      The inline assembler will incorrectly assemble certain JMPS and
  1264.      CALLs that are invalid and are rejected by the MASM and TASM
  1265.      assemblers. It will also incorrectly assemble JMPs and CALLs to
  1266.      destination declared with the ABSOLUTE directive. The bugs can
  1267.      be demonstrated by the following program:
  1268.  
  1269.      PROGRAM Jmp_Call;
  1270.  
  1271.      VAR AbsPointer: POINTER ABSOLUTE $1234:$5678;
  1272.          NormPointr: POINTER;
  1273.  
  1274.      PROCEDURE FarProc; FAR; ASSEMBLER;
  1275.      ASM
  1276.      END;
  1277.  
  1278.      PROCEDURE NearProc; NEAR; ASSEMBLER;
  1279.      ASM
  1280.      END;
  1281.  
  1282.      BEGIN
  1283.      ASM
  1284.         JMP  NEAR PTR AbsPointer   { This is illegal in MASM / TASM }
  1285.         JMP  FAR PTR AbsPointer    { incorrectly assembled by inline assmbl.! }
  1286.         JMP  AbsPointer            { This is illegal in MASM / TASM }
  1287.         JMP  NEAR PTR NormPointr   { This is illegal in MASM / TASM }
  1288.         JMP  FAR PTR NormPointr
  1289.         JMP  NormPointr
  1290.         JMP  NEAR PTR FarProc
  1291.         JMP  FAR PTR FarProc
  1292.         JMP  FarProc
  1293.         JMP  NEAR PTR NearProc
  1294.         JMP  FAR PTR NearProc
  1295.         JMP  NearProc
  1296.         CALL NEAR PTR AbsPointer   { This is illegal in MASM / TASM }
  1297.         CALL FAR PTR AbsPointer    { incorrectly assembled by inline assmbl.! }
  1298.         CALL AbsPointer            { This is illegal in MASM / TASM }
  1299.         CALL NEAR PTR NormPointr   { This is illegal in MASM / TASM }
  1300.         CALL FAR PTR NormPointr
  1301.         CALL NormPointr
  1302.         CALL NEAR PTR FarProc
  1303.         CALL FAR PTR FarProc
  1304.         CALL FarProc
  1305.         CALL NEAR PTR NearProc
  1306.         CALL FAR PTR NearProc
  1307.         CALL NearProc
  1308.      END;
  1309.      END.
  1310.  
  1311.  
  1312.      The instructions marked as "illegal in MASM / TASM" should be
  1313.      flagged as errors by the inline assembler. "JMP NEAR PTR AbsPointer"
  1314.      and "JMP NEAR PTR NormPointr" are near jumps to a different CS,
  1315.      and in "JMP AbsPointer", AbsPointer can't be addressed with the
  1316.      currently assumed registers. The same remarks apply to the
  1317.      equivalent CALL statements in the source. Consequently, the
  1318.      inline assembler produces garbage for the illegal statements.
  1319.      "JMP FAR PTR AbsPointer" should assemble to "JMP 1234:5678", but
  1320.      the inline assembler produces something very different. Instead
  1321.      of the absolute segment 1234h it uses the value of CS and in
  1322.      addition mangles the offset value. The assembly language program
  1323.      below, which is equivalent to the above PASCAL program, shows
  1324.      that "JMP FAR PTR AbsPointer" and "CALL FAR PTR AbsPointer" can
  1325.      be assembled correctly by TASM / MASM, so the inline assembler
  1326.      should do this as well.
  1327.  
  1328.  
  1329.                 DOSSEG
  1330.  
  1331.      AbsSeg     SEGMENT AT 1234h
  1332.                 ORG     5678H
  1333.      AbsPointer DD      ?
  1334.      AbsSeg     ENDS
  1335.  
  1336.  
  1337.      DATA       SEGMENT WORD PUBLIC 'DATA'
  1338.                 ASSUME  DS:DATA
  1339.      NormPointr DD      ?
  1340.      DATA       ENDS
  1341.  
  1342.  
  1343.      CODE       SEGMENT BYTE PUBLIC 'CODE'
  1344.                 ASSUME  CS:CODE, DS:DATA
  1345.  
  1346.      FarProc    PROC    FAR
  1347.                 RET
  1348.      FarProc    ENDP
  1349.  
  1350.      NearProc   PROC    NEAR
  1351.                 RET
  1352.      NearProc   ENDP
  1353.  
  1354.      Main:      MOV     AX, SEG (NormPointr)
  1355.                 MOV     DS, AX
  1356.      ;          JMP     NEAR PTR AbsPointer ; error !
  1357.                 JMP     FAR PTR AbsPointer
  1358.      ;          JMP     AbsPointer          ; error !
  1359.      ;          JMP     NEAR PTR NormPointr ; error !
  1360.                 JMP     FAR PTR NormPointr
  1361.                 JMP     NormPointr
  1362.                 JMP     NEAR PTR FarProc
  1363.                 JMP     FAR PTR FarProc
  1364.                 JMP     FarProc
  1365.                 JMP     NEAR PTR NearProc
  1366.                 JMP     FAR PTR NearProc
  1367.                 JMP     NearProc
  1368.      ;          CALL    NEAR PTR AbsPointer ; error !
  1369.                 CALL    FAR PTR AbsPointer
  1370.      ;          CALL    AbsPointer          ; error !
  1371.      ;          CALL    NEAR PTR NormPointr ; error !
  1372.                 CALL    FAR PTR NormPointr
  1373.                 CALL    NormPointr
  1374.                 CALL    NEAR PTR FarProc
  1375.                 CALL    FAR PTR FarProc
  1376.                 CALL    FarProc
  1377.                 CALL    NEAR PTR NearProc
  1378.                 CALL    FAR PTR NearProc
  1379.                 CALL    NearProc
  1380.  
  1381.      CODE       ENDS
  1382.  
  1383.  
  1384.      STACK      SEGMENT STACK
  1385.                 DB      100h DUP (?)
  1386.      STACK      ENDS
  1387.  
  1388.                 END     MAIN
  1389.  
  1390.  
  1391.  
  1392. 19. Other bugs in the inline assembler (ASM directive)
  1393.  
  1394.      Several instructions that have a format with an immediate operand
  1395.      support senseless or incorrect ranges for the immediate value. The
  1396.      IN, OUT, and INT instructions will accept values between -128 and
  1397.      255. Since negative values make no sense here, the possible range
  1398.      should be restricted to 0 to 255. The ENTER instruction will also
  1399.      take negative arguments. Again, this is not a very sensible choice.
  1400.      How are -5 bytes reserved for local variables? The allowed range
  1401.      for the arguments should be 0 to 255 and 0 to 65535, respectively.
  1402.      Although it is not officially documented by Intel, the AAM and AAD
  1403.      instructions may take additional arguments that indicate the base
  1404.      on which to perform the conversion. This is supported by the inline
  1405.      assembler. However, it accepts arguments between -128 and 127 while
  1406.      it should accept bases between 0 and 255, since the bases available
  1407.      with AAM and AAD must be positive.
  1408.  
  1409.      The inline assembler performs no check on the index in
  1410.      the stack top relative addressing mode of the coprocessor.
  1411.      Very large or even negative values are allowed. For example,
  1412.      FADD ST, ST(123456) will be accepted as perfectly legal.
  1413.      This must be fixed to make sure the index is between zero and
  1414.      seven.
  1415.  
  1416.      There is no way to code an absolute far jump such as JMP F000:FFF0
  1417.      (to perform a warm start). The same restriction applies to far
  1418.      calls. In a conventional assembler such a jump could be coded as
  1419.      follows:
  1420.  
  1421.      BIOS     SEGMENT AT 0F000h
  1422.      ORG      0FFF0h
  1423.      Restart  LABEL FAR
  1424.      BIOS     ENDS
  1425.  
  1426.      CODE     SEGMENT BYTE PUBLIC 'CODE'
  1427.      JMP      Restart
  1428.      CODE     ENDS
  1429.  
  1430.      There are no segment declarations available with the inline
  1431.      assembler, so the jump has to be either hand coded with DBs or
  1432.      changed to a memory indirect far jump using a appropriately
  1433.      initialized pointer. This problem should be documented in
  1434.      the Turbo-Pascal manuals.
  1435.  
  1436.      One of the standard syntax available with the IMUL instruction is
  1437.      not accepted by the inline assembler. IMUL reg16, immed8 is not
  1438.      allowed, rather the inline assembler expects this to be coded as
  1439.      IMUL reg16, reg16, immed8, where the two registers are identical.
  1440.      The IMUL reg16, immed8 is commonly accepted by assemblers and is
  1441.      also listed in Intel's documentation. Therefore, this syntax should
  1442.      be supported by the inline assembler.
  1443.  
  1444.      Some 286 protected mode instructions (LLDT, LMSW, LTR, SMSW, VERR,
  1445.      VERW) when used with memory operands require the use of the PTR
  1446.      directive to establish operand size with an untyped operand (e.g.,
  1447.      [BX+SI]). Two other instruction, SGDT and SIDT, do not require the
  1448.      use of PTR in these cases. This usage is inconsistent. Since the
  1449.      operand size can be deduced from the instructions itself (just as
  1450.      can be done in the case of a MOV AX, [BX]) no PTR directive at all
  1451.      should be required. Likewise, the POP mem16 instruction should not
  1452.      require a WORD PTR directive with an untyped memory operand, since
  1453.      memory operand size is obvious from the instruction.
  1454.  
  1455.  
  1456.  
  1457. 20. Errors / problems / documentation deficiencies using coprocessor
  1458.      instructions with the new ASM directive of TP 6.0
  1459.  
  1460.      In the $G+,N+ compiler mode the inline assembler does not
  1461.      assemble coprocessor instructions into emulator interrupts
  1462.      regardless of the $E switch setting. Instead it always generates
  1463.      optimized coprocessor instructions (without inserted WAITs).
  1464.      This causes programs compiled with $G+,N+,E+ to fail if no
  1465.      coprocessor is present. The assembler must ensure that the
  1466.      $E switch is off before performing this optimization.
  1467.  
  1468.      Coprocessor instructions in the no-wait form (e.g. FNINIT,
  1469.      FNSTSW, FNSTCW, FNSTENV, FNCLEX) are not encoded into emulator
  1470.      interrupts, since it makes no sense to use them with the
  1471.      emulator which cannot work in parallel with the CPU. This
  1472.      may lead to problems if programmers are not aware of the
  1473.      fact that these instructions will have absolutely no effect
  1474.      in an emulator environment. Since it is desirable to have the
  1475.      no-wait instructions available, programmers should be warned
  1476.      by the documentation not to use them in programs or routines
  1477.      that may be executed by the emulator or to explicitly code
  1478.      around this problem by using the system variable Test8087.
  1479.      An example of a work around solution follows.
  1480.  
  1481.      ASM
  1482.         .                         { some other code }
  1483.         .
  1484.         CMP   Test8087, 0         { coprocessor present ? }
  1485.         JNE   @Emulate            { no, do specific code for emulator}
  1486.         FNINIT                    { can be safely used with 8087 }
  1487.         JMP   @Continue           { skip emulator code }
  1488.  
  1489.         @Emulate:
  1490.         FINIT                     { this can be emulated }
  1491.  
  1492.         @Continue:                { continue with more code }
  1493.         .
  1494.         .
  1495.      END;
  1496.  
  1497.  
  1498.  
  1499. 21. TP wrongly flags coprocessor instruction as 286 specific
  1500.  
  1501.      Turbo Pascal's inline assembler BASM will not allow one to
  1502.      assemble the coprocessor instruction FLDLN2 (load Ln(2) to
  1503.      TOS). During compilation it gives a compile time error 159,
  1504.      "286/287 instructions are not enabled". However, FLDLN2 is
  1505.      by no means 286 specific, it is included in the Intel docu-
  1506.      mentation for the 8087 (see for example "Microprocessors,
  1507.      Volume 1", page 2-141, Intel 1991). TP 6.0 handles FLDLN2
  1508.      correctly in $G+ mode.
  1509.  
  1510.      {$N+,E-,G-}
  1511.  
  1512.      PROGRAM FLDBUG; { will not compile under TP 6.0 }
  1513.  
  1514.      BEGIN
  1515.         ASM
  1516.            FLDLN2 { TP 6.0 refuses to compile this in $G- mode }
  1517.            FSTP   ST(0);
  1518.         END;
  1519.      END.
  1520.  
  1521.  
  1522. 22. Inconsistent error messages emitted by inline assembler
  1523.  
  1524.      When constants that are out of range are supplied to assembler
  1525.      instructions that take some kind of immediate operand, two different
  1526.      error messages are emitted depending on the type of the destination
  1527.      operand. If the destination operand is a byte operand, as in
  1528.      ADD AL, 256 the compilation will result in error 155, 'Invalid
  1529.      combination of opcode and operands'. However, if the destination
  1530.      is a word operand as in ADD AX, 65536 the resulting error will be
  1531.      #76, 'Constant out of range'. This discrepancy should be resolved
  1532.      by always emitting the 'Constant out of range' error when an
  1533.      immediate value is not within the specified limits called for by the
  1534.      destination operand.
  1535.  
  1536.      Instructions that require one of their operands to be a memory
  1537.      reference (BOUND, LDS, LES, LEA, SGDT, SIDT, LGDT, LIDT) should
  1538.      cause compile error 156 (memory reference expected) to be emitted
  1539.      when a register is supplied instead of a memory reference. This
  1540.      will give a more detailed description of the error than the
  1541.      currently used error 155 (invalid combination of opcode and
  1542.      operand).
  1543.  
  1544.      There are space saving sign extending encodings available for OR,
  1545.      AND, and XOR instructions that the inline assembler fails to use.
  1546.      These encodings are the equivalents of the sign extending encodings
  1547.      used with the ADD, ADC, SUB, SBB, and CMP instructions. A list of
  1548.      the additional instructions follows:
  1549.  
  1550.      Instruction          | Encoding
  1551.      ---------------------+-------------------------------------------
  1552.      OR  reg16, const8    | 83   mod 001 r/m    data8
  1553.      OR  mem16, const8    | 83   mod 001 r/m   (disp)   (disp)   data8
  1554.      AND reg16, const8    | 83   mod 100 r/m    data8
  1555.      AND mem16, const8    | 83   mod 100 r/m   (disp)   (disp)   data8
  1556.      XOR reg16, const8    | 83   mod 110 r/m    data8
  1557.      XOR mem16, const8    | 83   mod 110 r/m   (disp)   (disp)   data8
  1558.  
  1559.  
  1560. 23. Compiler Switch /V doesn't export names of SYSTEM routines
  1561.  
  1562.      When using the /V of TPC or choosing standalone debugging within
  1563.      the Turbo Pascal IDE, all public identifiers are supposed to be
  1564.      included into the EXE file for debugging purposes. However, the
  1565.      names of just about every routine from the SYSTEM unit are not
  1566.      included, although the variables (such as HeapOrg) are included.
  1567.      Among the few exceptions are the MemAvail and MaxAvail routines,
  1568.      which are sometimes included into the debug information. This bug
  1569.      is very annoying when programs are profiled with the Turbo Profiler
  1570.      and one wants to know how much time the program spends in certain
  1571.      SYSTEM routines. Also, when debugging programs with Turbo Debugger
  1572.      one would rather like the disassembly to display a call as e.g.
  1573.      CALL SYSTEM.LONGMUL instead of a cryptic CALL 152E:05B8. This makes
  1574.      the disassembled code hard to follow. I therefore urge Borland to
  1575.      assure correct inclusion of *all* public symbols in the debug
  1576.      information generated by the /V switch.
  1577.  
  1578.  
  1579.  
  1580. 24. Problem with AAM xx and AAD xx instructions when stepping/tracing
  1581.     through inline ASM code with Turbo-Pascal's build-in debugger
  1582.  
  1583.      The inline assembler correctly allows parameters with the AAM and
  1584.      AAD instructions. Although this feature is not officially documented
  1585.      by Intel, it works on all 80x86 processors and compatibles, such as
  1586.      NEC's V30. The inline assembler will correctly assemble an instruction
  1587.      like AAM 16, which is quite useful when one wants to print a number
  1588.      in hexadecimal format. Turbo-Pascal's internal debugger does not
  1589.      recognize AAM opcodes other than the plain AAM opcode. When stepping/
  1590.      tracing through inline assembly code, it seems to skip the instruction,
  1591.      causing the program to behave differently than in an ordinary run.
  1592.      For example, the following instruction sequence will give 0505 in AX
  1593.      when run on any 80x86, but will give 0055 in AX when stepped through
  1594.      with Turbo-Pascal's internal debugger.
  1595.      .
  1596.      .
  1597.      MOV  AX, 0055h
  1598.      AAM  16
  1599.      .                   { AX should contain 0505h now }
  1600.  
  1601.      Another example involving the AAD instruction will give 0066h in AX
  1602.      when simply run, but AX will contain 00CEh when the code is stepped.
  1603.      .
  1604.      .
  1605.      MOV  AX, 0606h
  1606.      AAD  16
  1607.      .                   { AX should contain 0066h now }
  1608.  
  1609.  
  1610.  
  1611. 25. Error in heap manager (GetMem, New)
  1612.  
  1613.      Turbo-Pascal 6.0 allows memory allocation functions to allocate
  1614.      data structures of more than 65528 bytes on the heap. Data
  1615.      structures on the heap of size greater than 65528 bytes may
  1616.      cause segment wrap-around, thereby destroying other data on the
  1617.      heap or causing a general protection exception on processors
  1618.      from the 80286 on upwards. This general protection exception
  1619.      #GP(0) is triggered when a word is accessed at offset FFFFh in
  1620.      a segment, even when the processor is in real mode. With no valid
  1621.      #GP(0) handler present, the system will crash upon returning
  1622.      from the INT 0Dh service routine since the exception has pushed
  1623.      an error code *after* pushing the return address, which will not
  1624.      be removed from the stack without a valid #GP(0) handler present
  1625.      when the INT ODh executes it's IRET. 386 memory managers like
  1626.      QEMM or the DOS-box of windows in 386-Enhanced catch a #GP(0)
  1627.      exception, but plain DOS, even with MS-DOS 5.0, crashes. The
  1628.      following program illustrates the problems:
  1629.  
  1630.  
  1631.      PROGRAM HeapBug;
  1632.  
  1633.      TYPE SpcRecord  = RECORD
  1634.                           W1: WORD;
  1635.                           W2: WORD;
  1636.                           B1: BYTE;
  1637.                        END;
  1638.           SmallArray = ARRAY [1..8] OF CHAR;
  1639.           BigArray   = ARRAY [1..65535] OF CHAR;
  1640.           SpcArray   = ARRAY [1..13107] OF SpcRecord;
  1641.  
  1642.  
  1643.      VAR P1  : ^SmallArray;
  1644.          P2  : ^BigArray;
  1645.          P3  : ^SpcArray;
  1646.          Hptr: POINTER;
  1647.  
  1648.      BEGIN
  1649.         HPtr := HeapPtr;           { save initial value of heap pointer }
  1650.         WHILE HeapPtr = HPtr DO BEGIN
  1651.            New (P1);               { use up blocks in freelist }
  1652.         END;
  1653.         IF Ofs (HeapPtr^) <> 8 THEN
  1654.            New (P1);               { make sure large array will have ofs of 8 }
  1655.         New (P2);
  1656.         FillChar (P1^, 8, 'A');    { initialize 1st array }
  1657.         FillChar (P2^, 65534, 'B');{ initialize 2nd array -> trashes 1st array }
  1658.         IF P1^[6] <> 'A' THEN      { chk if 1st array's integrity was violated }
  1659.            WriteLn ('First array trashed!');
  1660.         P3 := Pointer (P2);
  1661.         P3^[13106].W2 := $55AA;    { access at ofs FFFF causes #GP(0) on 80286 }
  1662.      END.
  1663.  
  1664.      The problem here is that 80x86 segments start at 16-byte boundaries
  1665.      (paragraph boundaries), while allocation of data structures on the
  1666.      heap is aligned at 8-byte boundaries. If a data structure in the
  1667.      heap has a start address with an offset of 8 and is greater than
  1668.      65528 bytes, accessing the very last bytes of that data structure
  1669.      will cause undesired segment wrap around. Therefore, maximum allowed
  1670.      allocation for data structures on the heap should be 65528 bytes.
  1671.  
  1672.  
  1673.  
  1674. 26. Logical error in GRAPH.TextWidth function
  1675.  
  1676.      The TextWidth function delivers uncorrect results when fonts
  1677.      are scaled with the SetUserCharSize procedure. To compute the
  1678.      width of the string passed to it, the TextWidth function adds
  1679.      the width of all characters in the string. Depending on the
  1680.      current setting of the Direction parameter within GRAPH the
  1681.      resulting value is then multiplied and divided by either the
  1682.      MultX and DivX or the MultY and DivY scaling factors. If these
  1683.      scaling factors are not unity, this method will compute the
  1684.      wrong text width. Since text justification using the OutTextXY
  1685.      and SetTextJustify procedures relies on the TextWidth function
  1686.      for computing the starting position for string output, this
  1687.      output is not correctly justified. The TextWidth function, when
  1688.      used with user supplied font scaling factors, usually returns
  1689.      a width that is bigger than the actual width of the string. The
  1690.      correct way to compute text width is to compute the actual size
  1691.      of every character in the string using the scale factors supplied
  1692.      by the user and add these values up. An example:
  1693.  
  1694.      Suppose we want to compute the width of the string 'World'.
  1695.      Assume that the unscaled width of the characters as taken from
  1696.      the font information is 10, 7, 7, 5, 7, the output direction is
  1697.      horizontal and that the scale factors are MultX = 5 and DivX = 8.
  1698.      The current implementation of TextWidth would compute the width
  1699.      as ((10+7+7+5+7) * 5) DIV 8 = (36 * 5) DIV 8 = 180 DIV 8 = 22.
  1700.      A correct implementation however, would calculate the width as
  1701.      follows: (10*5) DIV 8 + 3 * (7*5) DIV 8 + (5*5) DIV 8 = (6+12+3)
  1702.      = 21. This version is correct since it uses character sizes as
  1703.      used in the OutText and OutTextXY procedures.
  1704.  
  1705.  
  1706.  
  1707. 27. Length of descender not taken into account by SetTextJustify
  1708.  
  1709.      If text is to be written at the very bottom of the current
  1710.      graphics window, one uses SetTextJustify (AnyMode, BottomText)
  1711.      and OutTextXY (AnyX, ViewMaxY, AnyText) to accomplish that.
  1712.      However, if the text contains letters that decend below the
  1713.      base line for letters, descenders are outside the window and
  1714.      clipped off. If one wants to output text in the manner described,
  1715.      this is very annoying, since the programmer has to adjust the
  1716.      Y-coordinate himself according to the font size in effect. The
  1717.      same problem occurs if text is to be written horizontally at
  1718.      the very right of the graphics window. Obviously, the TextHeight
  1719.      function used by the SetTextJustify procedure does not account
  1720.      for descender length. To fix the problem described, justification
  1721.      should be changed to account for the overall height of characters
  1722.      including descenders.
  1723.  
  1724.  
  1725.  
  1726.  
  1727. 28. "Snow" prevention fails on CGA due to unsafe algorithm
  1728.  
  1729.      The internal DirectWrite routine of module CRT is designed to prevent
  1730.      "snow" when writing directly to the CGA screen. However, a logical
  1731.      error prevents that this snow-checking works 100% safe. The same
  1732.      critisism applies to the WriteView method in module VIEWS of Turbo
  1733.      Vision. The following is an excerpt from CRT.DirectWrite:
  1734.  
  1735.      .
  1736.      .
  1737.      @@2:    LODSB             ; 1; get char
  1738.              MOV     BL,AL     ; 2;
  1739.      @@3:    IN      AL,DX     ; 3; wait until out of current horiz. sync,if in
  1740.              TEST    AL,1      ; 4;
  1741.              JNE     @@3       ; 5;
  1742.              CLI               ; 6;
  1743.      @@4:    IN      AL,DX     ; 7; wait until next horiz. sync starts
  1744.              TEST    AL,1      ; 8;
  1745.              JE      @@4       ; 9;
  1746.              MOV     AX,BX     ;10;
  1747.              STOSW             ;11; write to screen
  1748.              STI               ;12;
  1749.              LOOP    @@2       ;13;
  1750.      .
  1751.      .
  1752.  
  1753.  
  1754.      If an interrupt occurs after line 3 and before line 6 in the
  1755.      above code fragment, the program will *not* wait for the *start* of
  1756.      the horizontal sync but only test if the CGA is *in* a horizontal
  1757.      sync upon returning from the interrupt service routine. Since
  1758.      horizontal sync allows only for the output of exactly one character
  1759.      if output starts at the very beginning of horizontal sync, there
  1760.      is a good chance that the above program writes to the screen after
  1761.      the horizontal sync has been completed, thereby causing the CGA to
  1762.      "snow". Of course, failure of the above code to prevent "snow" is
  1763.      only noticeable in a system with very high interrupt rates e.g.
  1764.      running serial communication as a background TSR. One additional
  1765.      disadvantage of the above code is that it makes only use of the
  1766.      horizontal sync period, although this is much shorter than the
  1767.      vertical retrace period.
  1768.  
  1769.      The following enhanced code is 100% safe to prevent snow and uses
  1770.      the vertical and horizontal retrace periods. It has been tested on
  1771.      an original IBM-CGA. Interrupt latency is only marginally higher than
  1772.      with the original code and still allows to run interrupt driven
  1773.      serial communication at the highest possible rate of 115000 baud.
  1774.  
  1775.      DirectWrite:
  1776.  
  1777.                CMP     SI, DI              ; start address = end address ?
  1778.                JE      EmptyStr            ; yes, nothing to write
  1779.                PUSH    CX                  ; save
  1780.                PUSH    DX                  ;  registers
  1781.                PUSH    DI                  ;   that
  1782.                PUSH    DS                  ;    must be
  1783.                PUSH    ES                  ;     preserved
  1784.                MOV     CX, DI              ; string end address
  1785.                SUB     CX, SI              ; number of characters to write
  1786.                MOV     DL, CheckSnow       ; get flag for snow check
  1787.                MOV     DH, TextAttr        ; get current attribute
  1788.                XOR     AX, AX              ; address BIOS data area
  1789.                MOV     DS, AX              ;  via segment 0
  1790.                MOV     AL, DS:CrtWidth+400h; width of scan line in current mode
  1791.                MUL     BH                  ; multiply by cursor y-position
  1792.                XOR     BH, BH              ; clear hi-byte to prepare for addition
  1793.                ADD     AX, BX              ; add cursor x-position
  1794.                ADD     AX, AX              ; two screen bytes for every character
  1795.                XCHG    AX, DI              ; offset into screen memory to DI
  1796.                MOV     AX, DS:Addr6845+400h; get 6845 base address
  1797.                ADD     AX, 6               ; 6845 status port
  1798.                XCHG    AX, DX              ; AX = CheckSnow/TextAttr, DX = port
  1799.                MOV     BX, 0B800H          ; screen segment for color modes
  1800.                CMP     DS:CrtMode+400h, 7  ; monochrome mode ?
  1801.                JNE     ColorMode           ; no, one of the color modes
  1802.                MOV     BH, 0B0H            ; screen at segment B000h if mono
  1803.      ColorMode:PUSH    ES                  ; address character string
  1804.                POP     DS                  ;  via DS
  1805.                MOV     ES, BX              ; extra segment addresses screen seg
  1806.                CLD                         ; autoincrement for string instruct.
  1807.                OR      AL, AL              ; CheckSnow = TRUE ? (AH=attribute)
  1808.                JE      OutLoop             ; no, don't check for snow
  1809.      WriteChr: LODSB                       ; get character to write, AH = attrib
  1810.                XCHG    AX, BX              ; save character/attribute to write
  1811.      WaitHor:  CLI                         ; interrupts disturb critical timing
  1812.                IN      AL, DX              ; read 6845 status
  1813.                TEST    AL, 8               ; in vertical retrace ?
  1814.                JNZ     WriteScr            ; yes, it is safe to write to screen
  1815.                TEST    AL, 1               ; in horizontal retrace ?
  1816.                JNZ     WaitHor             ; yes, wait until out of hor. retrace
  1817.      WaitHor2: IN      AL, DX              ; read 6845 status
  1818.                TEST    AL, 1               ; horizontal or vertical retrace ?
  1819.                JZ      WaitHor2            ; no, wait until either kind of retr.
  1820.      WriteScr: XCHG    AX, BX              ; in horiz. or vert. retrace: get ch
  1821.                STOSW                       ; write character and attribute
  1822.                STI                         ; interrupts ok now
  1823.                LOOP    WriteChr            ; write next character until all thru
  1824.                JMPS    WriteDone           ; screen write done
  1825.      OutLoop:  LODSB                       ; get character to write
  1826.                STOSW                       ; write character and attribute
  1827.                LOOP    OutLoop             ; until all characters printed
  1828.      WriteDone:POP     ES                  ; restore
  1829.                POP     DS                  ;  destroyed
  1830.                POP     DI                  ;   registers
  1831.                POP     DX
  1832.                POP     CX
  1833.      EmptyStr: RET
  1834.  
  1835.  
  1836.  
  1837.  
  1838. 29. GetDir doesn't report use of invalid drive number
  1839.  
  1840.      The GetDir procedure should emit run time error 15 "Invalid
  1841.      drive number" when passed an invalid drive number. However, the
  1842.      procedure does not do the required check on the DOS return code
  1843.      and therefore never raises run time error 15. Instead, it always
  1844.      returns the String "X:\", where the X stands for any character
  1845.      in the IBM character set. The bug can easily be fixed by adding
  1846.      a few lines of code to the source module DIRH.ASM. The following
  1847.      program will demonstrate the bug:
  1848.  
  1849.  
  1850.      PROGRAM GetDirBug;
  1851.  
  1852.      VAR DriveNr:  INTEGER;
  1853.          PathName: STRING;
  1854.  
  1855.      BEGIN
  1856.         REPEAT
  1857.            Write  ('Enter Drivenumber (try also numbers > 100, 99 exits): ');
  1858.            ReadLn (DriveNr);
  1859.            GetDir (DriveNr, PathName);
  1860.            WriteLn('The path on drive ', DriveNr, ' is ', PathName);
  1861.         UNTIL DriveNr = 99;
  1862.      END. {GetDirBug}
  1863.  
  1864.  
  1865.  
  1866.  
  1867. 30. Help bug
  1868.  
  1869.      Context sensitive help (Ctrl-F1) for the predefined arrays Port
  1870.      and PortW is missing. There was no help for these arrays in TP5.5
  1871.      as well.
  1872.  
  1873.  
  1874.  
  1875. 31. Problems with the file selector box in IDE
  1876.  
  1877.      The history list of a file selector box contains only those
  1878.      files that were selected entering the file name in the input
  1879.      box, not those selected by double clicking the name in the
  1880.      file list, which is the standard way to select a file if the
  1881.      mouse is heavily used. Even when working mainly with the mouse
  1882.      a history list is still useful, since the desired files may
  1883.      be at the end of a file list 100 files long and one has to
  1884.      get to the right part of the file list before being able to
  1885.      double click the file name. By the way, this is also a problem
  1886.      on the Apple Macintosh, since its file select boxes do not
  1887.      have a history list feature at all. This can really be a pain
  1888.      in the neck. It is therefore strongly recommended that all
  1889.      files that have been selected with either method (that is, by
  1890.      entering the name in the input box or by double clicking the
  1891.      name in the file list) be put in the history list.
  1892.  
  1893.  
  1894.  
  1895. 32. Possible problems in unit APP.PAS
  1896.  
  1897.      APP.PAS contains a assembler function ISqr, that computes the
  1898.      integral part of the square root of its integer argument. This
  1899.      function has several shortcomings. First of all, it should more
  1900.      appropriately named ISqrt. Then, for all arguments > 32760, it
  1901.      will enter an endless loop. Finally it is not very fast, since
  1902.      it makes use of the IMUL instruction. Unfortunately, it is not
  1903.      clear to me, if the shortcomings pointed out cause any threat
  1904.      to program integrity. If it is desirable to fix the function,
  1905.      the following substitute could be used. It uses a more elegant
  1906.      and faster algorithm and returns the correct result for all
  1907.      positive INTEGERs. The code length is identical to the original
  1908.      routine ISqr.
  1909.  
  1910.      { ISqrt (I) computes INT (SQRT (I)), that is, the integral part of the }
  1911.      { square root of integer I. It does not check for negative arguments.  }
  1912.      { For all arguments 0..MaxInt the correct result is returned. The      }
  1913.      { algorithm exploits the following property:                           }
  1914.      {          n                                                           }
  1915.      {  n**2 =  Sigma (2i-1)                                                }
  1916.      {          i=1                                                         }
  1917.  
  1918.      FUNCTION ISqrt (I: INTEGER): INTEGER; ASSEMBLER;
  1919.  
  1920.      ASM
  1921.            MOV   CX, I   { load argument }
  1922.            MOV   AX, -1  { init result }
  1923.            CWD           { init odd numbers to -1 }
  1924.            XOR   BX, BX  { init perfect squares to 0 }
  1925.      @loop:INC   AX      { increment result }
  1926.            INC   DX      { compute }
  1927.            INC   DX      {  next odd number }
  1928.            ADD   BX, DX  { next perfect square }
  1929.            CMP   BX, CX  { perfect square > argument ? }
  1930.            JBE   @loop   { until square greater than argument }
  1931.      END;
  1932.  
  1933.  
  1934.  
  1935.  
  1936. 33. Poor performance of REAL type arithmetic
  1937.  
  1938.      Although this does not constitute a real bug, an analysis of the
  1939.      poor performance of the REAL type arithmetic will be given. The
  1940.      rationale here is that a 'TURBO product' should also deliver
  1941.      turbo performance wherever it can be achieved. One obvious example
  1942.      that there is ample room for speed improvements is the REAL-Sqrt
  1943.      function. It will take more time to compute the square root to 12
  1944.      decimal places than the coprocessor emulator needs to compute the
  1945.      function result to 19 decimal places. I feel that such a performance
  1946.      is unacceptable. Unfortunately, there were no improvements in TP6.0
  1947.      over TP5.5.
  1948.  
  1949.      Improvements are also possible in the LONGINT arithmetic, especially
  1950.      the division, which will enjoy accelerations of factor four to six
  1951.      (depending on the CPU) when coded using the DIV instruction.
  1952.  
  1953.      Performance can be enhanced by careful register scheduling within
  1954.      all routines, thus avoiding unnecessary memory accesses. This
  1955.      measure will also reduce the overall instruction count for a routine.
  1956.      Wherever possible, time saving CPU instructions such a MUL or DIV
  1957.      should be used. This will vastly improve performance especially on
  1958.      the 286, 386, and 486 CPUs. Most important is the choice of the
  1959.      appropriate algorithm for each function. Tests show that the REAL
  1960.      division uses the slowest out of four possible algorithms. This
  1961.      clearly indicates that not much time was invested in finding short
  1962.      but fast algorithms. On the other hand, the square rooting routine
  1963.      uses a basically fast algorithm (Newton's iteration), but
  1964.      obliterates it advantages by poor implementation. The trancendental
  1965.      functions are based on polynomial approximations. It seems that no
  1966.      care was taken to find the shortest and most accurate polynomials
  1967.      possible. The speed advantages possible by a careful recoding of
  1968.      the complete REAL arithmetic range from a few percent for simple
  1969.      functions like LONGINT to REAL conversion to up to a factor of 20
  1970.      for the Sqrt function.
  1971.  
  1972.  
  1973.  
  1974. 34. Inefficient string handling
  1975.  
  1976.      The string handling operations Insert, Delete, and Pos have
  1977.      always been implemented in a very simple but quite ineffient
  1978.      manner in Turbo-Pascal. There were no improvements in Turbo-
  1979.      Pascal 6.0. Since an acceleration of 300% - 400% can be
  1980.      achieved, this is hard to accept.
  1981.  
  1982.  
  1983. ***  Note: The above mentioned improvements have been realized in a
  1984.            replacement for the original SYSTEM.TPU. The source has been
  1985.            made available to BORLAND, but will not be given here. The
  1986.            library replacement (not the source though) is available
  1987.            as TPL60N15.ZIP via anonymous FTP from garbo@uwasa.fi
  1988.  
  1989.  
  1990.  
  1991.  
  1992. ++++++++++++++++++++ Suggestions for enhancements ++++++++++++++++++++++++++
  1993.  
  1994.  
  1995. 1. Suggested improvements for coprocessor / emulator arithmetic
  1996.  
  1997.      The routine that patches the emulator interrupts (INT 34 to INT 3D)
  1998.      back to coprocessor instructions at runtime if a coprocessor is
  1999.      present always insert WAITs (9Bh) before the coprocessor instruction.
  2000.      However, for all coprocessors except the 8087 these WAITs are
  2001.      unnecessary, since the 287 and 387 synchronize with the CPU at
  2002.      hardware level, using ports F0h thru FFh. These WAITs can therefore
  2003.      be replaced by NOPs, resulting in somewhat faster code. Performance
  2004.      improvements of up to 6% were observed with programs that make heavy
  2005.      use of simple coprocessor instructions (linear equation solver) by
  2006.      this simple change. A new routine, which does insert NOPs instead
  2007.      of WAITs where approriate is presented here.
  2008.  
  2009.  
  2010.      CODE    SEGMENT BYTE PUBLIC 'CODE'
  2011.  
  2012.              ASSUME  CS:CODE
  2013.  
  2014.      JMPS    EQU     <JMP SHORT>
  2015.  
  2016.      ;------------------------------------------------------------
  2017.      ; PATCH87 is the routine responsible for converting emulator
  2018.      ; interrupts back to coprocessor opcodes if a coprocessor is
  2019.      ; detected by the startup code.
  2020.      ;
  2021.      ; This routine is 1 byte shorter than the original one and has
  2022.      ; been enhanced to generate NOPs instead of WAITs before each
  2023.      ; coprocessor instruction when the coprocessor is a 287 or 387.
  2024.      ;
  2025.      ; INPUT:     No input or output. The desired sideeffect is
  2026.      ; OUTPUT:    patching the code at run-time.
  2027.      ;
  2028.      ; DESTROYS:  -
  2029.      ;
  2030.      ; All rights reserved (c) 1988, 1989, 1990, 1991 Norbert Juffa
  2031.      ;
  2032.      ; Borland is free to use this code if desired !
  2033.      ;-------------------------------------------------------------
  2034.  
  2035.      PATCH87 PROC  FAR
  2036.              PUSH  BP                ; save TURBO-Pascal framepointer
  2037.              MOV   BP, SP            ; make new framepointer
  2038.              PUSH  AX                ; save
  2039.              PUSH  SI                ;  destroyed
  2040.              PUSH  DS                ;   registers
  2041.              TEST  BYTE PTR [BP+7], 2; interrupts allowed before int ?
  2042.              JZ    $intdis           ; no
  2043.              STI                     ; yes, enable interrupts
  2044.      $intdis:LDS   SI, [BP+2]        ; load return address
  2045.              DEC   SI                ; point to int data
  2046.              MOV   AX, WORD PTR [SI] ; get interrupt number & data
  2047.              DEC   SI                ; point to patch
  2048.              SUB   AL, 34h           ; 34..3D --> 0..9
  2049.              CMP   AL, 9             ; interupt valid (between 0..9) ?
  2050.              JA    $invald           ; invalid interrupt
  2051.              JE    $fwait            ; interrupt $3D --> FWAIT
  2052.              CMP   AL, 8             ; interrupt $3C ?
  2053.              JE    $spcial           ; yes, handle segment overrides
  2054.              ADD   AL, 0D8h          ; new opcode
  2055.      $tst286:MOV   AH, AL            ; second byte of opcode
  2056.              MOV   AL, 90h           ; first byte is a nop
  2057.              PUSH  SP                ; test if
  2058.              POP   BP                ;  286 or
  2059.              CMP   SP, BP            ;   higher
  2060.              JE    $patch            ; 286
  2061.              MOV   AL, 9Bh           ; convert nop to wait
  2062.      $patch: MOV   WORD PTR [SI], AX ; store new opcode
  2063.              MOV   BP, SP            ; address stack via BP
  2064.              MOV   WORD PTR [BP+8],SI; set new return address
  2065.      $endptc:POP   DS                ; restore
  2066.              POP   SI                ;  destroyed
  2067.              POP   AX                ;   registers
  2068.              POP   BP                ; restore TURBO-Pascal frameptr
  2069.              IRET                    ; done
  2070.      $fwait: MOV   AX, 9B90h         ; store FWAIT
  2071.              JMPS  $patch            ; patch it in
  2072.      $spcial:TEST  AH, 20h           ; bit 5 set indicates spec. func.
  2073.              JNZ   $invald           ; not supported, invalid
  2074.              MOV   AL, AH            ; generate
  2075.              AND   AX, 07C0h         ;  segment
  2076.              SHR   AL, 1             ;   override
  2077.              SHR   AL, 1             ;    byte
  2078.              SHR   AL, 1             ;     and
  2079.              XOR   AL, 18h           ;      coprocessor
  2080.              ADD   AX, 0D826h        ;       opcode
  2081.              MOV   BYTE PTR [SI+2],AH; set new opcode
  2082.              JMPS  $tst286           ; put in new opcode
  2083.      $invald:JMPS  $endptc           ; no error handling, ignore
  2084.      PATCH87 ENDP
  2085.  
  2086.      CODE    ENDS
  2087.  
  2088.      END
  2089.  
  2090.  
  2091.      Another optimization could be performed if a program is
  2092.      compiled in the $N+,E- mode. Since no emulator is used
  2093.      anyhow, the compiler could give up generating emulator
  2094.      interrupts and generate real coprocessors instructions
  2095.      instead. On CPUs > 286 neither NOPs nor WAITs had to be
  2096.      inserted before NDP instructions. This would save space
  2097.      as well as time.
  2098.  
  2099.      Those functions that use the Borland shortcut interrupt 3Eh
  2100.      could test which NDP is present whenever this interrupt is
  2101.      called. If Test8087 = 3, the enhanced instructions (e.g. FSIN,
  2102.      FCOS) available on the 387/486/287XL could be executed. There
  2103.      would be only minimum timing overhead, but vast performance
  2104.      improvements on 386/486 machines. Since no elaborate argument
  2105.      reduction schemes are necessary, the additional code would be
  2106.      quite short.
  2107.  
  2108.      The Borland shortcut interrupt provides some functions not
  2109.      accessible from Turbo-Pascal 6.0. These functions are the tangent
  2110.      Tan (subcode F0h), the dyadic logarithm Ld (subcode F6h), the
  2111.      common logarithm Log (subcode F8h), power of two (subcode FCh),
  2112.      and power of ten (subcode FEh). Tests show that these undocumented
  2113.      functions are provided with a coprocessor as well as with the
  2114.      emulator and are fully operational. These functions should be
  2115.      made available to programmers through the SYSTEM unit and be
  2116.      documented. Especially the Tan is quite useful since it only
  2117.      takes 40% of the time of the equivalent construct Sin/Cos.
  2118.  
  2119.  
  2120.  
  2121. 2. Inclusion of LOADALL in inline assembler
  2122.      Since the undocumented AAM xx and AAD xx instructions are provided
  2123.      by the inline assembler, the undocumented LOADALL instruction
  2124.      (opcode 0F05h) could be provided as well when the compiler is in
  2125.      $G+ mode. The Turbo-Debugger will correctly disassemble LOADALL.
  2126.  
  2127.  
  2128.  
  2129. 3. Suggestions regarding 286 code generation feature ($G+)
  2130.  
  2131.      Programs compiled with the $G+ switch will have reduced memory
  2132.      requirements and will execute somewhat faster on a 286/386/486
  2133.      CPU. Typically, memory and time savings will not execeed 2%.
  2134.      Additionally, setting the $G switch on will allow the use of
  2135.      real and protected mode 286 instructions. As explained in section
  2136.      five of the README file, programs compiled with $G+ will not
  2137.      check for the presence of a approriate processor at runtime. It
  2138.      is strongly recommended that this behavior be changed. At least
  2139.      two cases are known (one involving Borlands biggest competitor)
  2140.      where programs were shipped that had been compiled with an 286
  2141.      switch setting. Customers using them on PC type machines were
  2142.      puzzled when they discovered that programs crashed on their systems
  2143.      although they had performed flawlessly on their office computer.
  2144.      Finally someone found the bug by tracing the program with a debugger.
  2145.      To avoid such unpleasant confusion, programs compiled with $G+
  2146.      should execute a short routine at startup to determine if an 286
  2147.      or later processor is present. If this is not the case, it should
  2148.      emit an error message and abort the program, just as programs
  2149.      compiled with $N+,$E- abort if they fail to detect a coprocessor.
  2150.  
  2151.      Since 286 real mode instructions can also be executed on NEC's
  2152.      V20/V30 processors and on the 80186/188, it might be desirable
  2153.      to have an 186 code generation feature. This would effectively
  2154.      split the $G switch into two separate switches. No changes would
  2155.      have to be made to the code generator, since it generates no 286
  2156.      protected mode instructions. Thus, generated code would be the
  2157.      same with either the 186 and 286 switches on. However, the inline
  2158.      assembler would only recognize protected mode instructions when
  2159.      the 286 switch is on. This would allow maximum utilization of the
  2160.      286 real mode instructions and a run time check for the CPU at the
  2161.      same time. Below is some code that can be used to distinguish between
  2162.      8086/8088, 80188/186/V20/V30, and 80286/386/486.
  2163.  
  2164.  
  2165.      ;--------------------------------------------------------------------
  2166.      ; CPU_Test distinguishes between three groups of CPUs commonly used
  2167.      ; in computers and returns an associated code for each.
  2168.      ;
  2169.      ; OUTPUT:  AX  = 0   Group #0 may execute 8086 code only (8086/8088)
  2170.      ;          AX  = 1   Group #1 may additionally execute 286 real mode
  2171.      ;                    instructions (V20/V30, 80186/80188)
  2172.      ;          AX  = 2   Group #2 may additionally execute 286 protected
  2173.      :                    mode instructions
  2174.      ;--------------------------------------------------------------------
  2175.  
  2176.      CPU_Test PROC    FAR
  2177.               PUSH    SP                   ; test updating
  2178.               POP     AX                   ;  of stackpointer
  2179.               CMP     AX, SP               ; stackpointer updated before push ?
  2180.               JE      @Grp2                ; no, must be 286, 386 or 486
  2181.               CLC                          ; make sure carry clear
  2182.               PUSHA                        ; PUSHA executed on 88/86 as JMP $+2
  2183.               STC                          ; carry set if V20/V30 or 186/188
  2184.      @8086:   JC      @Grp1                ; yes, its group #1
  2185.               XOR     AX, AX               ; CPU is 8088/8086
  2186.               RET                          ; done
  2187.      @Grp1:   POPA                         ; remove pushed bytes
  2188.               MOV     AX, 1                ; CPU is V20/V30 or 80186/80188
  2189.               RET                          ; done
  2190.      @Grp2:   MOV     AX, 2                ; CPU is 286/386/486
  2191.               RET                          ; done
  2192.      CPU_Test ENDP
  2193.  
  2194.  
  2195.  
  2196. 4. Suggestions for enhancements in the code generator
  2197.  
  2198.  4.1 Enhancing procedure entry/exit code in $G+ mode (286 code generation)
  2199.  
  2200.      When a procedure/function does not use local variables, the
  2201.      standard exit code in $G- mode is:
  2202.  
  2203.      POP   BP
  2204.      RET
  2205.  
  2206.      This is replaced by the following code in $G+ mode:
  2207.  
  2208.      LEAVE
  2209.      RET
  2210.  
  2211.      However, for procedures/function that have no local variables,
  2212.      it would be advantageous to always use the first sequence in
  2213.      either mode, $G- and $G+. Although both sequences take the same
  2214.      number of clock cylces on 286 and 386 processors, the first is
  2215.      considerably faster on the 486. Since the code generator already
  2216.      checks if no local variables are declared to generate optimized
  2217.      entry code in $G+ mode, the optimized exit code could be
  2218.      generated just as easily.
  2219.  
  2220.      Although the use of the ENTER imm16, 0 instruction does produce
  2221.      shorter code when a procedure/function has both, parameters and
  2222.      local variables, the equivalent but longer (two or three byte more)
  2223.      standard procedure entry code will execute faster than ENTER on
  2224.      all Intel processors. Therefore, it should be considered if it is
  2225.      really desirable to use ENTER at all. A lot of programs really
  2226.      do run slower on a 386DX machine if compiled with $G+ instead of
  2227.      $G-, as tests indicate.
  2228.  
  2229.      processor  |  ENTER imm16, 0    | standard entry sequence
  2230.      -----------+--------------------+------------------------
  2231.      80286      |  11 clocks         | 3 + 2 + 3 = 8 clocks
  2232.      80386      |  10 clocks         | 5 + 2 + 2 = 9 clocks
  2233.      80486      |  14 clocks         | 1 + 2 + 1 = 4 clocks
  2234.  
  2235.  
  2236.  
  2237.  4.2 Optimizing entry code for non nested procedures without parameters
  2238.      and local variables
  2239.  
  2240.      If a procedure/function takes neither any parameters nor declares
  2241.      any local variables and is not statically nested within another
  2242.      procedure/function, there is no need for any entry code. Turbo
  2243.      Pascal performs this optimization only for assembler procedures,
  2244.      but skips it for normal procedures, probably so that nested and
  2245.      non-nested procedures can use the same branch of the code
  2246.      generator. The code generator could be enhanced to generate
  2247.      procedure entry code only for those procedures that are either
  2248.      statically nested (and thus have a hidden parameter, namely the
  2249.      framepointer of the preceding procedure in the static chain),
  2250.      take parameters, or declare local variables.
  2251.  
  2252.  
  2253.  
  2254. 5. Suggestions for IDE
  2255.  
  2256.      The status line for the edit mode should be enhanced to include the
  2257.      shortcuts F5 Zoom and F6 Next. These additional hints will exactly
  2258.      fit into the remaining space. When IDE is in the stepping/debugging
  2259.      mode, shortcuts F4 Goto Cursor and Ctrl-F9 Run should be added to
  2260.      the status line. This would accelerate debugging sessions, since
  2261.      all program flow control could be excerted using simple mouse clicks
  2262.      on the status line.
  2263.  
  2264.  
  2265. 6. Suggestion regarding TURBO command-line options
  2266.  
  2267.      There should be a help switch like /? or /Help on the Turbo-Pascal
  2268.      Prorammer's Platform command line that displays a help screen
  2269.      which describes the other command-line switches that are available
  2270.      and explains what they will do.
  2271.  
  2272.