Appendix DAssembly Language (Machine Code) Subroutines

This appendix is written primarily for users experienced in assembly language programming.

GW-BASIC lets you interface with assembly language subroutines by using the USR function and the CALL statement.

The USR function allows assembly language subroutines to be called in the same way GW-BASIC intrinsic functions are called. However, the CALL statement is recommended for interfacing machine language programs with GW-BASIC. The CALL statement is compatible with more languages than the USR function call, produces more readable source code, and can pass multiple arguments.

D.1 Memory Allocation

Memory space must be set aside for an assembly language (or machine code) subroutine before it can be loaded. There are three recommended ways to set aside space for assembly language routines:

Specify an array and use VARPTR to locate the start of the array before every access.

Use the /m switch in the command line. Get GW-BASIC's Data segment (DS), and add the size of DS to reference the reserved space above the data segment.

Execute a .COM file that stays resident, and store a pointer to it in an unused interrupt vector location.

There are three recommended ways to load assembly language routines:

BLOAD the file. Use DEBUG to load in an .EXE file that is in high memory, run GW-BASIC, and BSAVE the .EXE file.

Execute a .COM file that contains the routines. Save the pointer to these routines in unused interrupt-vector locations, so that your application in GW-BASIC can get the pointer and use the routine(s).

Place the routine into the specified area.

If, when an assembly language subroutine is called, more stack space is needed, GW-BASIC stack space can be saved, and a new stack set up for use by the assembly language subroutine. The GW-BASIC stack space must be restored, however, before returning from the subroutine.

D.2 CALL Statement

CALLvariablename[(arguments)]

variablename contains the offset in the current segment of the subroutine being called.

arguments are the variables or constants, separated by commas, that are to be passed to the routine.

For each parameter in arguments, the 2-byte offset of the parameter's location within the data segment (DS) is pushed onto the stack.

Figure D.1 shows the state of the stack at the time of the CALL statement:

Figure 1

Figure D.1 Stack Layout When the CALL Statement is Activated not shown

The user's routine now has control. Parameters may be referenced by moving the stack pointer (SP) to the base pointer (BP) and adding a positive offset to BP.

Upon entry, the segment registers DS, ES, and SS all point to the address of the segment that contains the GW-BASIC interpreter code. The code segment register CS contains the latest value supplied by DEF SEG. If no DEF SEG has been specified, it then points to the same address as DS, ES, and SS (the default DEF SEG).

Figure D.2 shows the condition of the stack during execution of the called subroutine:

Figure 2

Figure D.2 Stack Layout During Execution of a CALL Statement not shown

The following seven rules must be observed when coding a subroutine:

The called routine may destroy the contents of the AX, BX, CX, DX, SI, DI, and BP registers. They do not require restoration upon return to GW-BASIC. However, all segment registers and the stack pointer must be restored. Good programming practice dictates that interrupts enabled or disabled be restored to the state observed upon entry.

The called program must know the number and length of the parame- ters passed. References to parameters are positive offsets added to BP, assuming the called routine moved the current stack pointer into BP; that is, MOV BP,SP. When 3 parameters are passed, the location of PO is at BP+10, P1 is at BP+8, and P2 is at BP+6.

The called routine must do a RETURN n (n is two times the number of parameters in the argument list) to adjust the stack to the start of the calling sequence. Also, programs must be defined by a PROC FAR statement.

Values are returned to GW-BASIC by including in the argument list the variable name that receives the result.

If the argument is a string, the parameter offset points to three bytes called the string descriptor. Byte 0 of the string descriptor contains the length of the string (0 to 255). Bytes 1 and 2, respectively, are the lower and upper eight bits of the string starting address in string space.

Note

The called routine must not change the contents of any of the three bytes of the string descriptor.

Strings may be altered by user routines, but their length must not be changed. GW-BASIC cannot correctly manipulate strings if their lengths are modified by external routines.

If the argument is a string literal in the program, the string descriptor points to program text. Be careful not to alter or destroy your program this way. To avoid unpredictable results, add +"" to the string literal in the program. For example, the following line forces the string literal to be copied into string space allocated outside of program memory space:

20 A$="BASIC"+""

The string can then be modified without affecting the program.

Examples:

100 DEF SEG=&H2000
110 ACC=&H7FA
120 CALL ACC(A,B$,C).
.
.

Line 100 sets the segment to 2000 hex. The value of variable ACC is added into the address as the low word after the DEF SEG value is left-shifted four bits (this is a function of the microprocessor, not of GW-BASIC). Here, ACC is set to &H7FA, so that the call to ACC executes the subroutine at location 2000:7FA hex.

Upon entry, only 16 bytes (eight words) remain available within the allocated stack space. If the called program requires additional stack space, then the user program must reset the stack pointer to a new allocated space. Be sure to restore the stack pointer adjusted to the start of the calling sequence on return to GW-BASIC.

The following assembly language sequence demonstrates access of the parameters passed and storage of a return result in the variable C.

Note

The called program must know the variable type for numeric parameters passed. In these examples, the following instruction copies only two bytes:

MOVSW

This is adequate if variables A and C are integer. It would be necessary to copy four bytes if they were single precision, or copy eight bytes if they were double precision.

MOV BP,SP

Gets the current stack position in BP

MOV BX,8[BP]

Gets the address of B$ description

MOV CL,[BX]

Gets the length of B$ in CL

MOV DX,1[BX]

Gets the address of B$ string descriptor in DX

MOV SI,10[BP]

Gets the address of A in SI

MOV DI,6[BP]

Gets the pointer to C in DI

MOVSW

Stores variable A in 'C'

RET 6

Restores stack; returns

D.3 USR Function Calls

Although the CALL statement is the recommended way of calling assembly language subroutines, the USR function call is still available for compatibility with previously-written programs.

Syntax:

USR[n](argument)

n is a number from 0 to 9 which specifies the USR routine being called (see DEF USR statement). If n is omitted, USR0 is assumed.

argument is any numeric or string expression.

In GW-BASIC a DEF SEG statement should be executed prior to a USR function call to ensure that the code segment points to the subroutine being called. The segment address given in the DEF SEG statement determines the starting segment of the subroutine.

For each USR function call, a corresponding DEF USR statement must have been executed to define the USR function call offset. This offset and the currently active DEF SEG address determine the starting address of the subroutine.

When the USR function call is made, register AL contains the number type flag (NTF), which specifies the type of argument given. The NTF value may be one of the following:

NTF Value

Specifies

2

a two-byte integer (two's complement format)

3

a string

4

a single-precision floating point number

8

a double-precision floating point number

If the argument of a USR function call is a number (AL<>73), the value of the argument is placed in the floating-point accumulator (FAC). The FAC is 8 bytes long and is in the GW-BASIC data segment. Register BX will point at the fifth byte of the FAC. Figure D.3 shows the representation of all the GW-BASIC number types in the FAC:

Figure 3

Figure D.3 Number Types in the Floating Point Accumulator not shown

If the argument is a single-precision floating-point number:

BX+3 is the exponent, minus 128. The binary point is to the left of the most significant bit of the mantissa.

BX+2 contains the highest seven bits of mantissa with leading 1 suppressed (implied). Bit 7 is the sign of the number (0=positive, 1=negative).

BX+1 contains the middle 8 bits of the mantissa.

BX+0 contains the lowest 8 bits of the mantissa.

If the argument is an integer:

BX+1 contains the upper eight bits of the argument.

BX+0 contains the lower eight bits of the argument.

If the argument is a double-precision floating-point number:

BX+0 through BX+3 are the same as for single precision floating point.

BX-1 to BX-4 contain four more bytes of mantissa. BX-4 contains the lowest eight bits of the mantissa.

If the argument is a string (indicated by the value 3 stored in the AL register) the (DX) register pair points to three bytes called the string descriptor. Byte 0 of the string descriptor contains the length of the string (0 to 255). Bytes 1 and 2, respectively, are the lower- and upper-eight bits of the string starting address in the GW-BASIC data segment.

If the argument is a string literal in the program, the string descriptor points to program text. Be careful not to alter or destroy programs this way (see the preceding CALL statement).

Usually, the value returned by a USR function call is the same type (integer, string, single precision, or double precision) as the argument that was passed to it. The registers that must be preserved are the same as in the CALL statement.

A far return is required to exit the USR subroutine. The returned value must be stored in the FAC.

D.4 Programs That Call Assembly Language Programs

This section contains two sample GW-BASIC programs that

load an assembly language routine to add two numbers together

return the sum into memory

remain resident in memory

The code segment and offset to the first routine is stored in interrupt vector at 0:100H.

The assembly language subroutine called in the above program must be assembled, linked, and converted to a .COM file. The program, when executed prior to the running of the GW-BASIC program, will remain in memory until the system power is turned off, or the system is rebooted.