Appendix D
Assembly 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:

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

CALL variablename[(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.

The GW-BASIC return address code segment (CS), and offset (IP) are pushed onto the stack.

A long call to the segment address given in the last DEF SEG statement and the offset given in variablename transfers control to the user's routine.

The stack segment (SS), data segment (DS), extra segment (ES), and the stack pointer (SP) must be preserved.

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:

  1. 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.
  2. 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.
  3. 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.
  4. Values are returned to GW-BASIC by including in the argument list the variable name that receives the result.
  5. 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.


  6. 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.
  7. 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,SPGets 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
MOVSWStores variable A in 'C'
RET 6Restores 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 ValueSpecifies
2a two-byte integer (two's complement format)
3a string
4a single-precision floating point number
8a 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:

If the argument is an integer:

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

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

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

Example 1 calls an assembly language subroutine:

Example 1

10 DEF SEG=0
100 CS=PEEK(&H102)+PEEK(&H103)*256
200 OFFSET=PEEK(&H100)+PEEK(&H101)*256
250 DEF SEG
300 C1%=2:C2%=3:C3%=0
400 TWOSUM=OFFSET
500 DEF SEG=CS
600 CALL TWOSUM(C1%,C2%,C3%)
700 PRINT C3%
800 END

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.

org 100H
0100 double segment
assume cs:double
0100 EB 17 90 start: jmp start1
0103 usrprg proc far
0103 55 push bp
0104 8B EC mov bp,sp
0106 8B 76 08 mov si,[bp]+8 ;get address of parameter b
0109 8B 04 mov ax,[si] ;get value of b
010B 8B 76 0A mov si,[bp]+10 ;get address of parameter a
010E 03 04 add ax,[si] ;add value of a to value of b
0110 8B 7E 06 mov di,[bp]+6 get address of parameter c
0113 89 05 mov di,ax ;store sum in parameter c
0115 5D pop bp
0116 ca 00 06 ret 6
0119 usrprg endp
;Program to put procedure in
;memory and remain resident. The
;offset and segment are stored
;in location 100-103H.
0119 start1:
0119 B8 00 00 mov ax,0
011C 8E D8 mov ds,ax ;data segment to 0000H
011E BB 01 00 mov bx,0100H ;pointer to int vector 100H
0121 83 7F 02 0 cmp word ptr [bx],0
0125 75 16 jne quit ;program already run, exit
0127 83 3F 00 cmp word ptr2 [bx],0
012A 75 11 jne quit ;program already run exit
012C B8 01 03 R mov ax,offset usrprg
012F 89 07 mov [bx],ax ;program offset
0131 8C c8 mov ax,cs
0133 89 47 02 mov [bx+2],ax ;data segment
0136 0E push cs
0137 1F pop ds
0138 BA 0141 R mov dx,offset veryend
013B CD 27 int 27h
013D quit:
013D CD 20 int 20h
013F veryend:
013F double ends
end start

Example 2 places the assembly language subroutine in the specified area:

Example 2

10 I=0:JC=0
100 DIM A%(23)
150 MEM%=VARPTR(A%(1))
200 FOR I=1 TO 23
300 READ JC
400 POKE MEM%,JC
450 MEM%=MEM%+1
500 NEXT
600 C1%=2:C2%=3:C3%=0
700 TWOSUM=VARPTR(A%(1))
800 CALL TWOSUM(C1%,C2%,C3%)
900 PRINT C3%
950 END
1000 DATA &H55,&H8b,&Hec &H8b,&H76,&H08,&H8b,&H04,&H8b,&H76
1100 DATA &H0a,&H03,&H04,&H8b,&H7e,&H06,&H89,&H05,&H5d
1200 DATA &Hca,&H06,&H00