Passing+data+to+assembler+code

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

Passing values in registers

 * __Advantages__: Simple, low code overhead, easy return of result via USR.
 * __Disadvantages__: Limited to integer values, maximum of four parameters can be passed.

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:

code format="bb4w" A% = par1% B% = par2% C% = par3% D% = par4% CALL address code and the skeleton assembler code would need to be:

code format="asm" .address ; here eax=par1%, ebx=par2%, ecx=par3%, edx=par4% ; do something useful with the data ret code If you additionally wish to return a 32-bit integer result from the assembler code back to BASIC you can do the following:

code format="bb4w" A% = par1% B% = par2% C% = par3% D% = par4% result% = USR(address) code and the skeleton assembler code would be:

code format="asm" .address ; here eax=par1%, ebx=par2%, ecx=par3%, edx=par4% ; do something useful with the data ; load eax register with result value ret code 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:

code format="bb4w" result% = FNfunction(par1%, par2%, par3%, par4%) code where the function is defined as follows:

code format="bb4w" DEF FNfunction(A%, B%, C%, D%) = USR(address) code

Using global memory locations

 * __Advantages__: Simple, no limit to number of parameters and results.
 * __Disadvantages__: Inelegant, unclear what is happening, difficult to maintain.

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**:

code format="bb4w" DIM p1% 3, p2% 3, p3% 3, p4% 3 !p1% = par1% !p2% = par2% !p3% = par3% !p4% = par4% CALL address code where the corresponding assembler code would be:

code format="asm" .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 code Here is the equivalent, but reserving the memory locations in the assembler code:

code format="bb4w" !p1 = par1% !p2 = par2% !p3 = par3% !p4 = par4% CALL address code where the corresponding assembler code would be:

code format="asm" .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 code In fact, in the case of this particular example, the assembler code could access the original variables directly:

code format="asm" .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 code The same techniques can be used to return one or more results back to BASIC.

Using CALL's parameter block

 * __Advantages__: Very flexible, all variable types (including arrays) can be passed.
 * __Disadvantages__: Relatively complicated, constants cannot be passed.

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:

code format="bb4w" CALL address, par1%, par2%, par3%, par4% code where the corresponding assembler code would be:

code format="asm" .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 code 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, , and ) - 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:

code format="bb4w" const% = 12345 CALL address, const% code 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.

Using SYS

 * __Advantages__: Can pass constant parameters
 * __Disadvantages__: Only integers and strings can be passed, slower than CALL

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:

code format="bb4w" SYS address, par1%, par2%, par3%, par4% code and the corresponding assembler code is:

code format="asm" .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 code 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:

code format="bb4w" SYS address, par1%, par2%, par3%, par4% TO result% code Alternatively you can pass as parameters the address(es) at which you want the result(s) to be stored.