Assembly-Language Externals (Advanced)

Top  Previous  Next

External subroutines can also be written in assembly language when you want maximum speed or need total control. Assembly-language subroutines are declared using the command word "external". The form is:

 

        external NAME(COMMENT), ... NAME(COMMENT);

 

"External" can be declared at any level of procedure nesting, unlike "eprocedure" and "efunction".

 

In the assembly code, the entry point label must be declared public. For example:

 

        CSEG    SEGMENT DWORD PUBLIC 'CODE'

                ASSUME  CS:CSEG

                PUBLIC  DOADD

 

        DOADD:  POP     CX              ;Save return address

                POP     DX              ;Save return segment

                POP     AX              ;Get second argument

                POP     BX              ;Get first argument

                ADD     AX,BX           ;Add arguments

                PUSH    AX              ;Return result

                PUSH    DX              ;Restore return address

                PUSH    CX

                RETF                    ;Far return to caller

 

        CSEG    ENDS

                END

 

This example takes two arguments that are passed on the stack, adds them and returns the result on the stack.

 

Like intrinsics, it is essential to keep the stack balanced by popping and pushing the correct number of arguments. Also, if you change the stack pointer (SP) or segment registers (CS, DS, SS, ES), they must be restored to their original values before you return. The other registers (AX, BX, CX, DX, SI, DI, BP) need not be preserved. The direction flag bit (D) also does not need to be preserved.

 

The last example shows one way of getting arguments on and off the stack using PUSH and POP instructions. Another way is to use BP to access all the arguments directly:

 

        DOADD:  MOV     BP,SP           ;Get stack pointer

                MOV     AX,[BP+4]       ;Get second argument

                ADD     [BP+6],AX       ;Add to first argument

                RETF    2               ;Drop one argument

 

Local variables can be created by putting them on the stack. This makes a subroutine reentrant, which enables it to be called by an interrupt routine in addition to the XPL0 program (it also enables it to call itself--recursively). For example:

 

                SUB     SP,4            ;Reserve space for two integers

                MOV     BP,SP           ;Get stack pointer

                MOV     AX,[BP]         ;Access one variable

                ADD     AX,[BP+2]       ;Access the other

                ADD     SP,4            ;Drop the local variables

 

You can also create local variables by defining blocks of data using DB, DW, DQ, and so forth, but this makes the subroutine non-reentrant. These variables must be in a segment declared like this:

 

        DSEG    SEGMENT WORD PUBLIC 'DATA'

        COLOR   DB      0

        PIXEL   DW      0

        DSEG    ENDS

 

This declaration tells the linker to group your data with the rest of the variables in the program. If you leave the DSEG directive out, your local variables will collide with variables in the XPL0 code. It is also important to link NATIVE last, otherwise similar problems occur.

 

Accessing global variables is a little more complicated. Generally it is best to pass any variables as arguments. You can even pass the address of a global variable the way arrays are passed. However, global variables can also be accessed using the public label "HEAPLO".

 

HEAPLO is the bottom of the heap memory space, which is where global variables start. HEAPLO is a public label defined in NATIVE. An assembly-language subroutine can use HEAPLO if HEAPLO is declared external (EXTRN). For example:

 

        \MAIN XPL0 PROGRAM

        \Start of global declarations

        integer Frog, Pig, Cow;

        . . .

        ;EXTERNAL ASSEMBLY-LANGUAGE SUBROUTINE

        EXTRN   HEAPLO:WORD             ;Declare HEAPLO as an external

        FROG    EQU     HEAPLO+8        ;Define global variables

        PIG     EQU     HEAPLO+10

        COW     EQU     HEAPLO+12

 

        CSEG    SEGMENT DWORD PUBLIC 'CODE'

                MOV     AX,FROG         ;Accessing global FROG

                MOV     AX,PIG          ;Accessing global PIG

                MOV     COW,AX          ;Accessing global COW

        . . .

 

Note that global variables actually start at HEAPLO+8. The first eight bytes are used for a special variable called "global zero". This is used by functions to return values. Eight bytes are used so that reals can be returned as well as integers.

 

WARNING: Do not give your external assembly-language file the same name as an .XPL file, otherwise when the .XPL file is compiled, you will lose your .ASM file.