Simulating+assembler+macros

//by Richard Russell, December 2008//

//This article supplements the information in the main Help documentation under [|Conditional assembly and macros]//.

The assembler built into //BBC BASIC for Windows// doesn't have a conventional [|macro] facility, but something similar can be simulated utilising the [|OPT] pseudo-instruction. **OPT** is normally used to control the assembler options (for example whether a listing is generated) but in this application it has two useful features: firstly it generates __no code__ and secondly it takes a numeric parameter. This provides a means of calling a user-defined function from within the assembler, and if that function itself outputs code (or data) it can behave like a macro.

One common use of a **macro** is to insert a block of code that you use frequently. Suppose your program often wants to find the [|absolute value] of the signed 32-bit number in the **eax** register, but you don't want the overhead of making a subroutine call to do so. The following code simulates a macro for the purpose:

code format="bb4w" DEF FNabs [OPT 0 cdq xor eax,edx sub eax,edx ]     = pass% code Now, whenever you want to calculate the absolute value of eax you can do so in your main assembler code as follows:

code format="bb4w" DIM code% 100, L% -1 FOR pass% = 8 TO 11 STEP 3 P% = code% [OPT pass% ... ; code which puts a signed value into eax OPT FNabs ... ; more code OPT FNabs ... ; etc.       ] NEXT pass% END code Here **FNabs** returns the current value of **pass%**, so in the main assembler loop the **OPT FNabs** pseudo-instructions simply set the assembler options (to what they already were). However the called function itself emits the opcodes corresponding to the absolute value calculation. You can see the effect by looking at the listing generated (note that the **OPT 0** in FNabs prevents any listing being generated there):

code 1002181A                               OPT pass% 1002181A                               ; code which puts a signed value into eax 1002181A 99 33 C2 2B C2                OPT FNabs 1002181F                               ; more code 1002181F 99 33 C2 2B C2                OPT FNabs 10021824                               ; etc. code Another way in which this facility can be used is to extend the assembler to handle instructions which are not supported natively. For example the instruction **SYSENTER** is available only on the Pentium Pro and later range of processors, and is therefore not supported in the //BBC BASIC for Windows// assembler. The following user-defined function can be used to 'add' that instruction:

code format="bb4w" DEF FNsysenter [OPT 0 DB &0F DB &34 ]     = pass% code Now, whenever you require to call the SYSENTER instruction, you can do that in your assembler code as follows:

code format="bb4w" OPT FNsysenter code A similar technique can be used when the instruction takes a parameter, for example a register name:

code format="bb4w" DEF FNfcmovb(reg$) [OPT 0 DB &DA DB &C0+VALRIGHT$(reg$) ]     = pass% code This implements the **FCMOVB** instruction which takes as a parameter an FPU register name (**st0** to **st7**). The routine automatically adjusts the emitted opcode according to the register specified (for simplicity the operand is not checked for validity, but it could be):

code format="bb4w" OPT FNfcmovb("st5") code With care the technique can be extended to instructions which take more complex operands.

Using labels in macros
If you need to use labels in the assembler code generated by a macro, care is needed to prevent a **Multiple label** error occurring when the macro is called more than once. If the labels are genuinely 'local' to the macro (this implies only 'backwards' references) then you can simply declare them as **LOCAL**:

code format="bb4w" DEF FNincarray LOCAL label [OPT 0 .label inc dword [ebx] add ebx,4 loop label ]     = pass% code Often you can rearrange code to ensure all references to labels are 'backwards' (i.e. the label is defined before the reference to it) but sometimes 'forward references' are unavoidable. In such cases the macro must be adapted so that a //different// label is created each time it is used. The easiest way of achieving that is to use an array element as the label:

code format="bb4w" DEF FNsimplisticabs(i%) PRIVATE label LOCAL label DIM label(10) label = label(i%) [OPT pass% AND &E test eax,eax jns label neg eax .label ret ]     label(i%) = label = pass% code Here the macro takes as a parameter an 'instance' value which is different for each use of the macro in your program:

code format="bb4w" DIM code% 100, L% -1 FOR pass% = 8 TO 11 STEP 3 P% = code% [OPT pass% ... ; code which puts a signed value into eax OPT FNsimplisticabs(0) ... ; more code OPT FNsimplisticabs(1) ... ; etc.       ] NEXT pass% END code As shown, the macro can be called up to ten times from the program (0 to 9); if this is insufficient the size of the **label** array may be increased.

If it's inconvenient to pass an 'instance' value each time the macro is called (and to ensure they are all different) this may be automated by checking whether the value of **pass%** has changed, and if so initialising the array subscript:

code format="bb4w" DEF FNsimplisticabs PRIVATE label, p%, i%     LOCAL label DIM label(10) IF p%<>pass% p% = pass% : i% = 0 label = label(i%) [OPT pass% AND &E test eax,eax jns label neg eax .label ret ]     label(i%) = label i% += 1 = pass% code
 * Note:** Although the interpreter accepts the direct use of array elements as assembler labels, which would allow the above code to be slightly simplified, this is not strictly valid syntax and is not permitted by the compiler.