ASSEMBLER MACRO TUTORIAL (DEVPAC) ================================= By Peter Hibbs and Simon Rigby An assembler MACRO definition is a system for equating a single word to a group of machine code instructions which can then be used in the program source code. The main object of MACROs is to make the source code easier to read and also easier to program. Used with care, MACROs can be a great help to the programmer although there are some pitfalls which should be avoided. Usually (but not always) the MACROs are defined in a separate file and 'included' in the main program source code which avoids cluttering up the source file. Since MACRO definitions must be defined BEFORE they are used in a program, the MACRO file must be included near the beginning of the source file. This tutorial refers entirely to HiSofts Devpac assembler. Although most modern assemblers can use MACROs there may be some differences in the details, refer to your assembler manual for further information. MACRO FORMAT ÿÿÿÿÿÿÿÿÿÿÿÿ To define a MACRO a label is used which is the name given to the MACRO followed by the machine code instructions and the ENDM command which terminates the MACRO definition. A simple example is shown below which executes a TOS call to wait for the next vertical sync pulse. v_sync MACRO movem.l d0-d2/a0-a2,-(sp) save registers move #37,-(sp) function 37 trap #14 call TOS trap 14 addq.l #2,sp correct stack movem.l (sp)+,d0-d2/a0-a2 restore registers ENDM In the user program the word v_sync is used in the instruction field wherever the above sequence of instructions is required. For example :- move #24,d0 load register loop v_sync execute macro dbra d0,loop repeat 23 times .. In the MACRO definition the words MACRO and ENDM are written in capitals, this is not necessary but is usually done to make the code easier to understand. Note that in this example the registers that are (or may be) used by the trap call are saved at the start and restored at the end. However, in all HiSofts MACROs and in the TOSMACRO.S file which accompanies this document, this is not the case. This highlights an important point concerning MACRO definition files especially where there are a large number in one file, that is that all MACROs should be properly documented so that the programmer knows exactly what each MACRO does, the parameters which need to be passed to it and any parameters returned. If this is not done properly a lot of time can be wasted trying to sort out why the MACRO code doesn't work especially as the MACRO code itself could be in another file hidden away somewhere on the disk. A text file should be written and printed out with the name of each MACRO and its required parameters and kept as a quick reference guide. MACRO SIZE ÿÿÿÿÿÿÿÿÿÿ A mistake that is easy to make is to overdo the MACRO size. It is not a good idea to make a MACRO too large, say more than about 10 lines of code. Don't forget that wherever the MACRO is used, all the code in the MACRO will be inserted into the object code. If a MACRO is used dozens of times in a program the source code file will still look nice and small but the object code will be much larger than it needs to be. Also debugging a program is more difficult, for example if you need to single step through a section of code which has several large MACROs it can lead to more wasted time since the chances are the MACRO code works OK as it has probably been tested elsewhere. For large blocks of code which are called repeatedly, a normal sub-routine should be used. Also, with HiSofts debugger, a sub-routine can be executed without having to single step through it which you cannot do with a MACRO. The sub- routine call itself could still be made into a MACRO, if required, although this is a bit pointless. BLACK BOX THEORY ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ Ideally all commonly used sub-routines should be made into a Macro file, as later changes in the way data is passed to the sub-routine, or additions to the number of parameters can be catered for in a macro, but would have to involve changing all your old program code (or making a new subroutine with a different name) otherwise. A case in point is Fsel_input which now has two GEM calls, one with a title and one without. The macro could be made to count the number of parameters passed and decide which one to use. PASSING PARAMETERS TO MACROs ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ It is often required to pass parameters to some of the instructions within the MACRO definition. Since these are not always known at assembly time there is a system for passing parameters to the MACRO during program execution. A common type of MACRO are the various TOS calls using the GEMDOS, BIOS and XBIOS some of which require other variables when used. For example, the GEMDOS call #2 (c_conout) which outputs a character to the screen requires the character to be displayed to be pushed onto the stack when called. This value can be passed to the MACRO as a constant, in a register or in a memory store when the program is run. It is defined in the MACRO itself as a \ character followed by an alpha-numeric character 1-9, a-z or A-Z. For example the c_conout MACRO could be defined as :- c_conout MACRO \1=character move \1,-(sp) push chr onto stack move #2,-(sp) function No trap #1 GEMDOS call addq.l #4,sp correct stack ENDM In the program source code the MACRO would be called like this :- c_conout #'A' and would display the character A. The \1 value is replaced with the data following the MACRO call. This value can be a constant (preceded by a # character), a register (d0-d7 or a0-a6) or a memory store without changing the MACRO itself. For example the following two MACROs both do the same thing as the one above :- move #'A',d0 c_conout d0 or move #'A',store c_conout store .. store ds.w 1 The assembler automatically works out what format the parameter is in and generates the object code accordingly. Up to 36 parameters can be passed to a MACRO, subsequent parameters should follow the first separated by commas. MACROs can also be nested as shown in the example below :- gemdos MACRO 1\function,2\stack size move \1,-(sp) push function number to stack trap #1 execute add \2,sp correct stack ENDM f_read MACRO 1\buffer,2\count,3\fhandle move.l \1,-(sp) push buffer address to stack move.l \2,-(sp) push count value to stack move \3,-(sp) push file handle to stack gemdos #63,#12 execute GEMDOS MACRO call 63 ENDM The first MACRO (gemdos) takes two parameters, the function number and the stack correction value which is then used by the second MACRO (f_read). So when the f_read MACRO is invoked as :- move.l #2000,d0 f_read #file_buff,d0,fhandle the code would be expanded to :- move.l #file_buff,-(sp) move.l d0,-(sp) move fhandle,-(sp) move #63,-(sp) trap #1 add.l #12,sp .. fhandle ds.w 1 file handle file_buff ds.b 2000 file buffer Note that it is not necessary to define the value sizes in the MACRO call as this is done in the MACRO definition itself although it is a good idea to specify the size in the document file to remind the user what size parameter is required. The # character is necessary for fixed values or addresses but not for registers or the contents of a memory store. Note also that the names shown on the line after the MACRO pseudo-op are comments to remind the programmer which parameters are being used within the MACRO. NEW INSTRUCTIONS ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ One trivial use for MACROs is to invent new instructions for the 68000 CPU. On some processors there are bit instructions BZ and BNZ which branch if a bit value is zero or non-zero. Although there are equivalents on the 68000 (beq and bne) they are not so obvious. The two MACROs below allow the use of 'bz' if a bit = 0 and 'bnz' if a bit <> 0 without increasing the number of instructions in the object code. bz MACRO beq \1 ENDM bnz MACRO bne \1 ENDM In the source code the instructions would be used in the normal way :- btst #1,d0 test bit 1 in reg d0 bz address branch if bit=0 or btst #0,flags test bit 0 in (flags) bnz address branch if bit<>0 TESTING PARAMETERS ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ When a MACRO requires parameters the programmer usually enters them into the code, however the MACRO itself can test whether a parameter is missing and generate an assembly error to warn that something is wrong. In the example above the MACRO can test whether the address has been provided after the bz or bnz instruction. For example :- bz MACRO IFC '','\1' test for missing parameter FAIL generate error MEXIT exit macro prematurely ENDC end of IF beq \1 ENDM The MACRO first checks if there is a string as a parameter and skips to the ENDC pseudo-op if there is. If there is no address parameter the FAIL pseudo-op generates an assembly error and then exits the MACRO with the MEXIT pseudo-op. If the MEXIT command is not included the assembler generates another error when it gets to the 'beq' instruction because of the missing address label. The line with the FAIL command is stored in the error file but the cursor is positioned on the bz instruction in the source file. NUMBER OF ARGUMENTS ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ It is sometimes necessary for a MACRO to know how many arguments have been passed to it from the main program. The NARG reserved symbol defines the number of arguments actually following a MACRO call and can be used within the MACRO using the IF pseudo-ops. One example is to check whether the correct number of arguments have been defined in the MACRO call. In the example below the rsconf MACRO requires six arguments which means that the NARG symbol will have the value six if all the arguments have been entered after the MACRO call, by checking that this value is six within the MACRO, the assembler can generate an error if it is incorrect. The MACRO definition would be as follows :- rsconf MACRO 1\scr,2\tsr,3\rsr,4\ucr,5\ctrl,6\baud IFNE 6-NARG if 6-NARG NotEqual to zero FAIL Invalid parameters MEXIT exit MACRO ENDC move \1,-(sp) move \2,-(sp) move \3,-(sp) move \4,-(sp) move \5,-(sp) move \6,-(sp) move #15,-(sp) trap #14 add #14,sp ENDM If less than (or more than) six parameters are used in the MACRO call the assembler will flag an error during assembly. Note that the symbol \# can also be used in place of the NARG symbol. INSTRUCTION SIZE ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ The symbol \0 can also be used to indicate an instruction size i.e. byte, word or longword. The following example (from the DevPac manual) illustrates this. An INC or DEC instruction can be easily simulated on the 68000 CPU with the following MACRO :- inc MACRO 1\register addq.\0 #1,\1 add 1 to register ENDM In the source code the instructions would be formed as :- inc.b d0 increment low byte or inc.w d1 increment low word or inc.l a0 increment whole register The \0 symbol passes the size of the register to the MACRO so that the correct part of the register is incremented. Note that if no size is provided the instruction defaults to .w (16 bits). The dec instruction will be the same except the addq will be subq. MACRO LABELS ÿÿÿÿÿÿÿÿÿÿÿÿ It is sometimes useful to have a branch (or jump) instruction within a more complicated MACRO. It is not possible, however, to use a normal label because if the MACRO is used more than once the assembler will see duplicate label symbols. The \@ symbol provides a method of using MACRO labels, the assembler generates a different unique label each time the MACRO is used. The example below shows this technique :- clr_screen MACRO move.l screen,a0 fetch screen address move #32000/4-1,d0 set loop counter \@ clr.l (a0)+ clear word and inc dbra d0,\@ dec count and repeat ENDM Another example using both of the above symbols is a Memory Move, where you can move bytes, words or longs and it shows how MACROS can improve efficiency at the same time... Move block of memory in bytes, words or longs, uses registers d0/a0-a1 Mem_move MACRO from,to,count (in bytes,words or longs) move.w \3,d0 move.l \1,a0 move.l \2,a1 .\@ move.\0 (a0)+,(a1)+ dbra d0,.\@ ENDM and in the program use one of the following :- Mem_move.b from_addr,to_addr,number_of_bytes Mem_move.w from_addr,to_addr,number_of_words Mem_move.l from_addr,to_addr,number_of_longs Note also that the 'full stop' character can be used as part of a label. MACRO TABLE ÿÿÿÿÿÿÿÿÿÿÿ The MACRO symbol definitions are stored in a separate table to the normal source code symbols so that MACRO names can be the same as labels without causing the assembler to flag up an error. For example the following sub-routine is valid if a little confusing :- v_sync movem.l d0-d2/a0-a2,-(sp) save registers v_sync call macro movem.l (sp)+,d0-d2/a0-a2 restore registers rts OTHER SYMBOLS ÿÿÿÿÿÿÿÿÿÿÿÿÿ There are a few more symbols associated with MACROs which are described in the DevPac manual but as they are not used very much I have not mentioned them in this document. MACRO FILE DOCUMENTATION ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ As mentioned above it is important to keep a printed document which details the function of each MACRO together with any parameters which are passed to it and any returned. Since MACRO files can get changed from time to time as more options are added or bugs corrected, it is also prudent to include the issue number and any changes that have been made to the file. These are best placed at the beginning of the MACRO file and the document file together with the description and date of each change. USING DEVPAC AND ICTARI MACRO FILES ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ HiSoft supply all the standard GEM function calls for the AES and VDI as a MACRO file with the DevPac assembler (GEMMACRO.I or GEMMACRO.S for DevPac 2) together with the AES and VDI library files. Since all programmers with DevPac will have these files, members should use the MACROs in any source code that is sent in to ICTARI to make the code shorter and easier to understand. Unfortunately HiSoft do not provide a MACRO file for the TOS system calls so we have provided two versions on this disk which can be used as a standard MACRO files. The file TOSMACRO.S in folder MACRO_1 defines all the TOS calls (but not the extra ones added to the STE TOS) in a simple form. The associated text file TOSMACRO.TXT shows the format for each MACRO in the source file. The second MACRO file is in the MACRO_2 folder and is called MACRO_V1.I which, in turn, uses the other .I files in the same folder. These MACROs are more comprehensive and include various error checking facilities. There is no separate text file but each MACRO has an explanation of its use within the source file which could be printed out for reference. The programmer can decide which one he wants to use (or even use both) and copy it/them to his INCDIR folder or wherever the 'include' files are stored. We would encourage members sending in source code which use TOS calls to use one of these MACRO files (don't forget to say which) and use the appropriate MACROs in the code to make it easier to understand. If members send in some source code which uses a MACRO which is NOT defined in the MACRO files just mentioned above, would they please include the MACRO definition in the source code itself or send in the MACRO file together with the source code. Obviously any MACROs that are used in the code are useless without the MACRO code itself. CONCLUSION ÿÿÿÿÿÿÿÿÿÿ It should be clear from this article that MACROs can improve the clarity of complex assembler source code providing they are used in moderation. It would be possible, for example, to make every small section of code into a MACRO which would then defeat the main aim of using MACROs. Also, creating standard MACRO files which all programmers can use, makes source code simpler and easier to follow which is the primary aim of a programmers user group such as ICTARI. If any members have any further comments to make regarding this subject, ICTARI would be very interested to hear them. ____________________