by Richard Russell, November 2006
When you incorporate assembly language code in your BBC BASIC program (either for speed or to do things which aren't possible in BASIC) you are very likely to want to pass data into, and possibly out of, the assembler code. There are a number of different ways to do this, each with its own particular advantages and disadvantages. This article discusses four alternative methods and illustrates each with a common example for ease of comparison: the passing of four integer numeric parameters from BASIC code into the assembler code.
When an assembly language routine is activated using CALL or USR the current values of the A%, B%, C% and D% static variables are copied into the processor's eax, ebx, ecx and edx registers respectively and may be used directly in the assembler code.
So if you need to pass four integer parameters par1%, par2%, par3% and par4% into the assembler code you would call it as follows:
A% = par1%
B% = par2%
C% = par3%
D% = par4%
CALL address
and the skeleton assembler code would need to be:
.address ; here eax=par1%, ebx=par2%, ecx=par3%, edx=par4% ; do something useful with the data ret
If you additionally wish to return a 32-bit integer result from the assembler code back to BASIC you can do the following:
A% = par1%
B% = par2%
C% = par3%
D% = par4%
result% = USR(address)
and the skeleton assembler code would be:
.address ; here eax=par1%, ebx=par2%, ecx=par3%, edx=par4% ; do something useful with the data ; load eax register with result value ret
A somewhat more elegant method of transferring the parameters and result, which avoids modifying the global values of A% to D%, is to use a function call thus:
result% = FNfunction(par1%, par2%, par3%, par4%)
where the function is defined as follows:
DEF FNfunction(A%, B%, C%, D%) = USR(address)
Memory locations reserved using DIM can be accessed in assembler code, and memory locations reserved using (for example) dd in assembler code can be accessed by BASIC. Hence it is possible to pass data into and out of assembler routines by using shared memory locations.
Here is how that method could be used to pass our four parameters, firstly using DIM:
DIM p1% 3, p2% 3, p3% 3, p4% 3 !p1% = par1% !p2% = par2% !p3% = par3% !p4% = par4% CALL address
where the corresponding assembler code would be:
.address mov eax,[p1%] ; now eax=par1% mov ebx,[p2%] ; now ebx=par2% mov ecx,[p3%] ; now ecx=par3% mov edx,[p4%] ; now edx=par4% ; do something useful ret
Here is the equivalent, but reserving the memory locations in the assembler code:
!p1 = par1%
!p2 = par2%
!p3 = par3%
!p4 = par4%
CALL address
where the corresponding assembler code would be:
.p1 dd 0 .p2 dd 0 .p3 dd 0 .p4 dd 0 .address mov eax,[p1] ; now eax=par1% mov ebx,[p2] ; now ebx=par2% mov ecx,[p3] ; now ecx=par3% mov edx,[p3] ; now edx=par4% ; do something useful ret
In fact, in the case of this particular example, the assembler code could access the original variables directly:
.address mov eax,[^par1%] ; now eax=par1% mov ebx,[^par2%] ; now ebx=par2% mov ecx,[^par3%] ; now ecx=par3% mov edx,[^par4%] ; now edx=par4% ; do something useful ret
The same techniques can be used to return one or more results back to BASIC.
The CALL statement accepts any number of parameters (following the routine's address) and information about these parameters is stored in a parameter block in memory. This contains the number of parameters plus the type (e.g. integer, float, string etc.) and address of each parameter. On entry to the assembler code the ebp register points to the start of this parameter block.
To use this method to pass our four integer parameters use code like the following:
CALL address, par1%, par2%, par3%, par4%
where the corresponding assembler code would be:
.address mov eax,[ebp+2] ; eax=^par1% mov eax,[eax] ; now eax=par1% mov ebx,[ebp+7] ; ebx=^par2% mov ebx,[ebx] ; now ebx=par2% mov ecx,[ebp+12] ; ecx=^par3% mov ecx,[ecx] ; now ecx=par3% mov edx,[ebp+17] ; edx=^par4% mov edx,[edx] ; now edx=par4% ; do something useful ret
This may seem more complicated than the previous methods, but the advantage is that you can pass not only integers but any kind of variable, including arrays and structures. Although the listed code doesn't bother to check the variable types (stored at “[ebp+1]”, “[ebp+6]”, “[ebp+11]” and “[ebp+16]”) - it assumes they are the expected integers - it could do so, either to accept different types or to check for errors.
You cannot include constants in CALL's list of parameters (a constant doesn't have a memory address!) so in the event that you need to do so you must copy the 'constant' into a variable first:
const% = 12345
CALL address, const%
You can just as easily pass values out of the assembler code as into it, because since the memory addresses of the parameters are stored in the parameter block the assembler code can modify the variables directly (with care!).
A good example of the flexibility of CALL is the SORTLIB library supplied with BBC BASIC for Windows. This will sort any number of arrays of any type: the assembler code examines the parameter types and chooses the appropriate sort routine.
The SYS statement is primarily intended for calling Windows API functions or other functions in DLLs (Dynamic Linked Libraries), however it is possible to use it to call your own assembler routines. To use it for our example the BASIC code required is as follows:
SYS address, par1%, par2%, par3%, par4%
and the corresponding assembler code is:
.address mov ebp,esp mov eax,[ebp+4] ; now eax=par1% mov ebx,[ebp+8] ; now ebx=par2% mov ecx,[ebp+12] ; now ecx=par3% mov edx,[ebp+16] ; now edx=par4% ; do something useful ret 16 ; discard parameters
Note particularly the ret 16; the number following ret must be four times the
number of parameters supplied.
You can return a single integer value in the same way as USR: the assembler code must return with the eax register containing the value and the SYS call must use TO to assign it to a variable:
SYS address, par1%, par2%, par3%, par4% TO result%
Alternatively you can pass as parameters the address(es) at which you want the result(s) to be stored.