



                                  A D D E N D U M


     Here are some new features of the XPL0 language that are not described in
     the manual.


     NEW INTRINSICS

     79: RanSeed(integer);

     This intrinsic sets the seed for the random number generator (1: Ran).
     This allows up to 65536 different, repeatable random number sequences.
     Previously a repeatable random sequence was set by using Ran(0), but this
     only provided a single sequence.


     80: Irq(boolean);

     This intrinsic turns interrupts on if boolean is "true", or off if
     boolean is "false". This is useful for instance when manipulating the
     VGA registers and you don't want the mouse interrupt service routine to
     interfere. The NMI interrupt is not affected. Interrupts, of course,
     should not be left off for very long. Be aware that most BIOS and DOS
     calls will temporarily re-enable interrupts.

     Some intrinsics can be generated as in-line code by the compiler, which
     makes them much faster. If Abs, Rem, Swap, or Extend are replaced with
     lowercase names (abs, rem, swap, extend) then in-line code is generated.
     As a bonus "abs" works equally well for integers and reals, thus RlAbs is
     no longer need. Since the compiler recognizes these lowercase names, it
     can optimize some constant calculations. For example swap($1234<<8) is
     generated as $0034. Lowercase intrinsics are not available in the inter-
     preted version (XPLI).

     A new command word "port" has also been added. This allows accessing I/O
     ports as though they were character (byte) arrays. For example:

             port($27A):= port($27A) ! $01;    \(set LPT2 strobe line high)

     This "port" feature is not available in the interpreted version (XPLI);
     the existing PIn and POut intrinsics must be used. PIn and POut must also
     be used to do 16-bit word I/O.



     2        ADDENDUM



     SWITCHES

     Command-line switches are used to modify the behavior of the compilers.
     Many of them are not normally used. However, for completeness of
     documentation they're all listed here.

     The command-line switches for XPLN are:

             /L  List source code to the monitor screen
             /A  divert Assembly code to monitor instead of the output file
             /C  insert I2L Comments into the output file

     XPLX also recognizes these switches:

             /D  Debug: include XPL0 source code in .ASM output file
             /2  align loops to word boundaries to speed them up
             /3  align loops to double-word boundaries to speed them up
             /S  generate near procedure calls for code Smaller than 64K
             /J  generate short conditional Jumps (to be fixed by MASM 6)
             /B  do short-circuit evaluation of Boolean expressions

     XPLI only recognizes these switches:

             /L  List source code to the monitor screen
             /D  list Debug information to the monitor screen
             /W  display Warning messages

     Procedure calls can be optimized if a program is less than 64K by using
     the /S (Small) switch. This replaces the normal far call instruction with
     a near call. Calls to external XPL0 procedures (eprocs) are handled the
     same as normal procedures. Calls to intrinsics and external assembly
     language routines are always far regardless of the /S switch. (These
     calls can be optimized a little using the /F switch in LINK.) If several
     modules are linked together, they must all be compiled the same way--
     either with /S or without it.

     Conditional jumps can be optimized using the /J switch and MASM 6. The /J
     switch makes XPLX output short conditional jumps, such as JNE. It relies
     on MASM 6 to automatically replace them with jumps over long jumps if
     necessary. For example, MASM 6 automatically replaces JNE with JE $+5 and
     JMP if the target location is more than 128 bytes away. It also replaces
     a normal 3-byte JMP with a short JMP wherever possible.

     TASM 3.1 does not support the /J switch, that is, it does not fix short
     jumps that are out of range. Nor does it support the environment variable
     INCLUDE, which tells MASM where to look for include files (such as
     RUNTIME.ASM) if they are not in the current directory.


                                                                ADDENDUM     3



     SHORT-CIRCUIT BOOLEANS

     Some boolean expressions can be executed much more quickly by using the
     /B command-line switch to enable short-circuit evaluation. Short-circuit
     evaluation is used in conditional statements to by-pass the rest of a
     boolean expression when the result is already known. For example:

             if A=3 ! B=5 ! C=7 ! D=11 then Prime:= true;

     In this expression if B is equal to 5 then there is no reason to compare
     C to 7 and D to 11 because Prime will be assigned the value "true"
     despite these additional comparisons.

     The reason this feature uses a switch rather than being done auto-
     matically is that it can cause some errors, although they're very
     unlikely.

     An error can occur if a term in the boolean expression contains a
     function call that does more than simply return a value. Such a function
     is said to have a "side effect", and it is generally considered bad
     programming. Here is an example:

             if P<10 & P/3=N then DoSomething;
             R:= Rem(0);

     Rem(0) is not defined when P is >= 10 and short-circuit evaluation is
     enabled. The divide operation not only returns the quotient, but also
     sets the remainder as a side effect.

     Another reason for not automatically using short-circuit evaluation is
     that some older programs might give a compile error unless a small
     modification is made. For instance, the statement: "while A | B do..."
     gives the new compile error 75: EXPRESSION MUST BE ENCLOSED IN
     PARENTHESES. Adding parentheses solves the problem: "while (A | B) do..."
     This problem only occurs with the exclusive-or operator and the "if"
     expression, and these are rarely used in the boolean expression of a
     conditional statement. For example: while (if A=1 then F1 else F2) do....

     Since expressions are evaluated from left to right, it's faster to test
     for frequent conditions on the right side, for example:

             if Ch>=^0 & Ch<=^9  !  Ch>=^A & Ch<=^F then DoHex;

     (Hint: There are ten digits in the range 0..9 and only six in the range
     A..F.) Also it's more efficient to do comparisons before testing flags.
     For example, this takes advantage of short-circuit evaluation:

             if Printer=Epson & Pin9 then ...

     4        ADDENDUM



     This does not:

             if Pin9 & Printer=Epson then ...

     Avoid using unnecessary parentheses because expressions enclosed in
     parentheses are not short-circuit evaluated.


     IN-LINE ASSEMBLY CODE

     XPL0 has the ability to handle assembly code that is inserted directly
     into an XPL program. The reserved word "asm" designates that the fol-
     lowing characters on the line are assembly code, and they are to be
     copied to the output (.ASM) file. For example:

             asm     cli
             asm     mov     ax, 102         ;comment
             asm     mov     bx, Frog        ;Comment

     Assembly code must be written in lowercase characters except when an XPL
     variable or constant name is used. These are written in the usual way
     with at least the first letter capitalized. This enables the compiler to
     distinguish them from the rest of the assembly code and to substitute
     them with their corresponding code. For instance, in the above example,
     "Frog" might be replaced with something like "[SI+4]". Capital letters
     may be used in comments because comments are ignored by the compiler.

     If several lines of assembly code are needed, they may be written this
     way:

             asm     {
                     cli
                     mov     ax, 102         ;comment
                     mov     bx, Frog        ;Comment
                     }

     The ability to insert assembly code into a high-level language program
     is a two-edged sword. In general it should be avoided, but there are
     instances when it is very useful.

     The most obvious application is to replace compiled code with more
     efficient assembly code. For instance "Irq(false)" can be replaced with
     "asm cli", which is at least ten times faster (except under Windows XP,
     which simulates the cli). Similarly, "POut(Time, $40, 1)" could be
     replaced with:

             asm     mov ax, Time
             asm     out 40h, ax


                                                                ADDENDUM     5



     Assembly language provides low-level control that a high-level language
     can't. Consider this expression: Frog * 777 / 1000. If Frog is above 42,
     the calculation will overflow in 16-bit XPL. However the following will
     not overflow even when Frog is 32767:

             asm     {
                     mov     ax, 777
                     imul    Frog            ;ax:dx := ax * Frog
                     mov     cx, 1000
                     idiv    cx              ;ax := ax:dx / cx
                     }

     Here is an example of a double-precision add:

             TimeLo:= TimeLo + 143;
             asm     {jnc    tm10
                      inc    TimeHi
                     tm10:};


     RULES AND RESTRICTIONS

     With the power of assembly language, it's easy to shoot yourself in the
     foot. When using XPLX, the SI and DI registers must not be altered, and
     of course altering DS, SS or CS is fatal.


     The compiler generates the correct code for named constants such as:
     "def Frog=123; asm mov ax, Frog". It also generates the correct code for
     variables except as follows. If the variable is at an intermediate level
     (neither local or global), the inserted assembly code must make sure the
     correct BASEn is loaded into the BP register. With XPLN the correct BASEn
     must be loaded in the SI register for all variables other than globals.

     Here is an example that fills an integer array with a pattern. It runs
     about seven times faster than the equivalent "for" loop in XPLX (and 24
     times faster than XPLN--as measured on a Duron 850).

             \XPLN needs the following line, but it must not be in XPLX
             \asm    mov     si, base1
             asm     {
                     push    ds              ;es:= ds
                     pop     es
                     push    di              ;save di register
                     mov     di, Array
                     mov     ax, Pattern
                     mov     cx, Size
                     cld                     ;set direction flag to increment
                     rep stosw               ;es:[di++] := ax; cx--
                     pop     di              ;restore di register
                     }

     6        ADDENDUM



     Segment variables are not supported.

     Line labels are allowed if they are in lowercase and don't conflict with
     names generated by the compiler. Certain names are reserved and cannot be
     used such as: l208, ll3, cseg, dseg, base2, intr10. The assembler will
     flag an error if there is a conflict.

     Expressions are evaluated by the assembler, not by the compiler. Thus,
     for instance, hex numbers are represented with a trailing "h" instead of
     a leading "$".

     The bracket "}" ends an assembly-code section, even if it occurs inside a
     "; comment", but not if occurs inside a "\ comment".


     WARNINGS

     A separate version of the run-time code is required for XPLX (XX.BAT).
     Code compiled with XPLX must be linked to NATIVEX, NATIVE7X, or NATX.
     Code compiled with XPLN must be linked to NATIVE, NATIVE7, or NAT. This
     is handled automatically when using the batch files X, XX, XN and XJSB.

     XJSB.BAT makes programs about 7K smaller than with XX.BAT. It accom-
     plishes this mostly by linking in NATX.OBJ instead of NATIVEX.OBJ. This
     small version of the run-time code does not support floating-point
     calculations nor does it support high-speed line draw (intrinsic 42:
     Line). XJSB also enables the /J /S /B switches. This means that MASM 6
     must be used to fix any short jumps, that the program code cannot be
     larger than 64K, and that short-circuit boolean evaluation is done.
     Floating point calculations will not give a compile error, but they
     will give the run-time errors 4 and 5 (unless they are turned off with
     the Trap intrinsic). Line draw still works, but it is many times slower
     than with the full version of the run-time code.

     Beware of STDLIB.OBJ. It must be compiled with the same compiler used to
     make other .OBJ files. If your main program module uses XPLX then
     STDLIB.XPL must be compiled using XPLX, not XPLN. One critical difference
     between these two compilers is that XPLX returns integer values from
     function calls in the AX register while XPLN returns them in global 0.
     Another difference is that XPLX uses the DI register as the heap pointer.
     Also, be sure that the /S switch is used consistently on all .XPL modules
     that are linked together.

     XPLX puts the control variable of a "for" loop in a register. Although
     unlikely, a possible problem is using a pointer to change this variable.
     For example, the following is not an infinite loop if compiled by XPLX,
     but it is if it's compiled by the other compilers:

             A:= addr I;
             for I:= 1, 10 do
                     A(0):= 3;

     XPLI's /W switch displays a warning message for this situation.

                                                                ADDENDUM     7



     Be aware that using the Reserve intrinsic to reserve an odd number of
     bytes in a character array can misalign the heap, which makes any
     variable declared from that point on (including in called procedures)
     misaligned, which can significantly slow a program. The Reserve intrinsic
     does not automatically align to a word boundary because some early
     programs took advantage of consecutive Reserves allocating contiguous
     space.

     Consecutive dimensioned arrays cannot allocate contiguous space, because
     the pointer to the array precedes the reserved space. Thus adding a
     byte to maintain word alignment is not a problem.

     It is more efficient to declare dimensioned arrays last, after all other
     variables have been declared. This allows single-byte offsets to address
     these variables, whereas if an array is declared with 128 bytes or more,
     then double-byte offsets are required.
