The alternative method is to turn the block of instructions into a macro, the method for doing this will be explained later. Essentially what this does is to give this block of instructions a name and, when the assembler comes across this name, it inserts the instructions of the macro into the object code which it is producing so that the CPU does not have to perform any jumps when it is executing the code.
Hence the main difference between macros and subroutines is that subroutines are called at run-time and macros are called at assembly time.
The advantage of macros is that they are faster than subroutines since no jumps are needed during execution. The disadvantage is that if the macro is to be used several times, the resulting machine code program will have to contain multiple copies of the instructions represented by the macro, and thus be long. Using a subroutine would only require one copy of the instructions. Macros can be more useful than just an aid to save typing however, and this chapter explains some of their other features. Further examples can be found in section 12.5 (General purpose macros).
ROR A : ROR A : ROR A : ROR A
This simply shifts the upper nibble of the accumulator into the lower nibble. A macro with the name 'FNrotateacc' containing this sequence can be set up outside the assembler program as follows:
DEF Fnrotateacc [OPT pass ROR A : ROR A ROR A : ROR A ] :=pass
This macro can then be called from inside the assembler with the statement
OPT FNrotateacc
The OPT statement is being used here as a dummy statement, simply to call FNrotateacc and have no other effect. We therefore arrange for the value of the function to be 'pass', which should be the value used in the initial OPT statement. Therefore on reaching this statement the assembler will generate the machine code corresponding to the assembler instructions of the macro, and place this in the object code. Then it will move on to the next instruction of the assembler program.
The flow of control when the program is being typed will look something like this:
The machine code produced will be as follows:
A5 81 LDA addr addr = &81 6A ROR A 6A ROR A 6A ROR A 6A ROR A 85 81 STA addr
DEF FNrotateacc(rotate) FOR number 1 TO rotate [OPT pass ROR A ] NEXT number = pass
So, to rotate the bits in any memory location any number of times to the right, simply set up a macro as follows:
DEF FNrotate(address, rotate) FOR number = 1 TO rotate [OPT pass ROR address ] NEXT number =pass
A typical call might be
OPT FNrotate(&3000, 4)
This would generate machine code to rotate right four times the bits in location &3000.
DEF FNoptimumrotate(rotate) IF rotate < 1 THEN = pass IF rotate < 5 THEN FOR number = 1 TO rotate : [OPT pass : ROL A:]:Next number ELSE FOR number = 1 TO (9 - rotate) : [OPT pass : ROR A:]:Next number = pass
DEFFNstar [OPT pass LDX addr BEQ exclamation LDA #ASC"*" JSR oswrch .exclamation LDA #ASC"!" JSR oswrch ] : =pass
When the assembler reaches the 'BEQ exclamation' loop instruction on the second pass, it will give the offset the same address of the label which was set up the first time around. If forward referencing is not used then this problem will not occur, but it is still undesirable to use label names time and again. The program becomes very difficult to follow and to debug.
To use labels in macros that are called from more than one place, it is necessary to set up tables of labels. Thus if the label 'start' is used in a macro, an array called 'start' would have to be DIMensioned at the beginning of the program together with as many elements as the number of times the macro is called. If the macro with 'start' in it was called three times from within the program, the statement 'DIM start(2)' would have to be inserted before the first call to that macro took place. Also, each call to the macro would have to pass a parameter which contained a number (0,1 or 2) so that the correct label would be used. To illustrate:
DEF FNmacro(fred,jim,no) [OPT pass LDA fred LDY jim .start(no) JSR oswrch DEY BNE start(no) ] =pass
The above macro will print the contents of 'fred' as an ASCII character, 'jim' times. By convention the label number is always passed as the last parameter, and it is also a good idea to have all the macros using the same variable to hold the number (in this case 'no').
A typical call to the above macro might be
OPT FNmacro(addr, 45, 1)
This would use the label 'start(1)'.