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. |