Document Navigation
Table Of Contents
Previous
Next

VII. MACROS

A macro is a facility for substituting a whole set of instructions and parameters in place of a single instruction or call to the macro. There are always two steps to the use of macros, the definition and the call. In the definition we specify what set of instructions make up the body of the macro and assign a name to it. This macro may then be called by name with a single assembler instruction line. This single line is replaced by the body of the macro or the group of lines which were defined as the macro. This replacement of the calling line with the macro body is called the macro "expansion". It is also possible to provide a set of parameters with the call which will be substituted into the desired areas of the macro body.

A simple example will assist in the understanding of macros. Let us define a macro which will shift the 'D' register left four places. This is such a simple operation that it does not really require or make effective use of macros, but it will suffice for learning purposes. In actuality this routine would probably be written in-line or, if required often, written as a subroutine.

The first step is to define the macro. This must be done BEFORE THE FIRST CALL to the macro. It is good practice to define all macros early in a program. The definition is initiated with a MACRO directive and terminated by an ENDM directive. The definition of our example would be as follows:

ASLD4 MACRO
ASLB
ROLA
ASLB
ROLA
ASLB
ROLA
ASLB
ROLA
ENDM

The first line is the MACRO directive. Note that the name of the macro is specified with this directive by placing it in the label field. This macro name should follow all the rules for labels. It will NOT be placed in the symbol table, but rather in a macro name table. The body of the macro follows and is simply lines of standard assembly source which shift the 'D' register left four places. The definition is terminated by the ENDM directive.

When this macro definition is encountered during pass 1, the assembler will not actually assemble the source, but instead copy it into a buffer for future access when the macro is called. During pass 2 this definition is ignored.

At this point we may continue with our assembly program and when we desire to have the 'D' register shifted left four places, simply call the macro as follows:

.
.
LDA VALUE
LDB VALUE+1
ASLD4 here is the macro call
STD RESULT
.
.

You can see that calling a macro consists of simply placing its name in the mnemonic field of a source line. When the assembler sees the above call, it realizes that the instruction is not a standard 6809 mnemonic, but rather a macro that has been previously defined. The assembler will then replace the ASLD4 with the lines which make up the body of that macro or "expand" the macro. The result would be the following assembled code:

.
.
LDA VALUE
LDB VALUE+1
ASLB the body of the macro
ROLA replaces the call
ASLB
ROLA
ASLB
ROLA
ASLB
ROLA
STD RESULT
.
.

You should note that a macro call differs from a subroutine call in that the macro call results in lines of code being placed in-line in the program where a subroutine call results in the execution of the routine at run-time. Five calls to a subroutine still only requires one copy of the subroutine while five calls to a macro results in five copies of the macro body being inserted into the program.

PARAMETER SUBSTITUTION

If macros were limited to what was described above, they would probably not be worth the space it took to implement them in the assembler. The real power of macros comes in "parameter substitution". By that we mean the ability to pass parameters into the macro body from the calling line. In this manner, each expansion of a macro can be different.

As an example, suppose we wanted to add three 16-bit numbers found in memory and store the result in another memory location. A simple macro to do this (we shall call it ADD3) would look like this:

ADD3 MACRO
LDD LOC1 get first value in 'D'
ADDD LOC2 add in second value
ADDD LOC3 add in third value
STD RESULT store result
ENDM

Now let's assume we need to add three numbers like this in several places in the program, but the locations from which the numbers come and are to be stored are different. We need a method of passing these locations into the macro each time it is called and expanded. That is the function of parameter substitution. The assembler lets you place up to nine parameters on the calling line which can be substituted into the expanded macro. The proper form for this is:

MACNAM <prm.l>,<prm.2>,<prm.3>,...,<prm.9>

where "MACNAM" is the name of the macro being called. Each parameter may be one of two types: a string of characters enclosed by like delimiters and a string of characters not enclosed by delimiters which contains no embedded spaces or commas. The delimiter for the first type may be either a single quote (') or a double quote (") but the starting and ending delimiter of a particular string must be the same. A comma is used to separate the parameters. These parameters are now passed into the macro expansion by substituting them for 2-character "dummy parameters" which have been placed in the macro body on definition. These 2-character dummy parameters are made up of an ampersand (&) followed by a single digit representing the number of the parameter on the calling line as seen above. Thus any occurence of the dummy parameter, "&1", would be replaced by the first parameter found on the calling line.

Let's re-do our ADD3 macro to demonstrate this process. The definition of ADD3 now looks like this:

ADD3 MACRO
LDD &l get first value in 'D'
ADDD &2 add in second value
ADDD &3 add in third value
STD &4 store result
ENDM

Now to call the macro we might use a line like:

ADD3 LOC1,LOC2,LOC3,RESULT

When this macro was expanded, the &l would be replaced with LOC1, the &2 would be replaced with LOC2, etc. The resulting assembled code would appear as follows:

LDD LOC1 get first value in 'D'
ADDD LOC2 add in second value
ADDD LOC3 add in third value
STD RESULT store result

Another call to the macro might be:

ADD3 ACE,TWO,LOC3,LOC1

which would result in the following expansion:

LDD ACE get first value in 'D'
ADDD TWO add in second value
ADDD LOC3 add in third value
STD LOC1 store result

Now you should begin to see the power of macros.

There is actually a tenth parameter which may be passed into a macro represented by the dummy parameter "&0". It is presented on the calling line as so:

<prm.0> MACNAM <prm.l>,<prm.2>,<prm.3>,...,<prm.9>

This parameter is somewhat different from the others in that it must be a string of characters that conform to the rules for any other assembly language label since it is found in the label field. It is in fact a standard label which goes into the symbol table and can be used in other statement's operands like any other label.

Ignoring a Dummy Parameter

There may be times when a programmer wishes to have an ampersand followed by a number in a macro which is not a dummy parameter and should therefore not be replaced with a parameter string. An example would be an expression where the value of MASK was to be logically 'anded' with the number 4. The expression would appear like:

MASK&4

If this expression were in a macro, upon expansion the &4 would be replaced with the fourth parameter on the calling line. It is possible to prevent this, however, by preceding the ampersand with a backslash (\) like this:

MASK\&4

When the assembler expands the macro containing this expression, it will recognize the backslash, remove it, and leave the ampersand and following number intact.

Another case where this can be useful is when a macro is defined within a macro (that is possible!) and you wish to place dummy parameters in the inner macro.

THE EXITM DIRECTIVE

Sometimes it is desireable to exit a macro prematurely. The EXITM directive permits just that. During expansion of a macro, when an EXITM command is encountered the assembler immediately skips to the ENDM statement and terminates the expansion. This probably does not seem logical , and is not except when used with conditional assembly.

To portray the use of EXITM, assume we have some macro called XYZ which has two parts. The first part should always be expanded, but the second should only be expanded in certain cases. We could use EXITM and the IFNC directives to accomplish this as follows:

XYZ MACRO
.
. code that should always be generated
.
IFNC &2,YES
EXITM
ENDIF
.
. code that is only sometimes generated
.
ENDM

The following calls would result in the second part being expanded or producing code:

XYZ "PARAMETER 1",YES
XYZ "PARAMETER 1","YES"
XYZ 0,YES

while all of the following would result in the second part not being expanded:

XYZ "PARAMETER 1",NO
XYZ JUNK,NO
XYZ JUNK
XYZ PRMI,PRM2

The EXITM directive itself requires no operand.

THE DUP AND ENDD DIRECTIVES

There is another type of assembler construct which may only be used inside of a macro, called the DUP-ENDD clause. The assembler will duplicate the lines placed between the DUP and ENDD (end dup) instructions some specified number of times. The proper form is:

DUP <dup count>
.
. code to be duplicated
.
ENDD

where the <dup count> is the number of times the code should be duplicated. The <dup count> may be any valid expression, but must be in the range of 1 to 255 decimal. Note that DUP-ENDD clauses may NOT be nested. That is to say, one DUP-ENDD clause may not be placed inside another.

As an example, let's take our first example in this section on macros and spruce it up a little. Assume we want a macro that will shift the 'D' register to the left 'x' places where 'x' can vary in different calls to the macro. The DUP-ENDD construct will work nicely for this purpose as seen here:

ASLDX MACRO
DUP &l
ASLB
ROLA
ENDD
ENDM

Now to shift the 'D' register left four places we call the macro with:

ASLDX 4

To shift it left 12 places we simply use the instruction:

ASLDX 12

And so on.

MORE ON MACROS

A few more hints on using macros may be of value.

One important thing to remember is that parameter substitution is merely replacing one string (the dummy parameter or &x) with another (the parameter string on the calling line). You are not passing a value into a macro when you have a parameter of "1000", but rather a string of four characters. When the expanded source code of the macro is assembled, the string may be considered a value, but in the phase of parameter substitution it is merely a string of characters. An example macro will help clarify this point.

TEST MACRO
LDA #$&l COMMENT FIELD IS HERE
LDB L&l
NOP PARAMETERS CAN EVEN BE
NOP SUBSTITUTED INTO
NOP COMMENTS &2
&3 OR THEY CAN BE A MNEMONIC
&4 TST M&1M OR LABEL OR INSIDE A STRING
ENDM

Now if this macro were called with the following command:

TEST 1000,'LIKE THIS',SEX,"LABEL"

The expanded source code would look like this:

LDA #$1000 COMMENT FIELD IS HERE
LDB L1000
NOP PARAMETERS CAN EVEN BE
NOP SUBSTITUTED INTO
NOP COMMENTS LIKE THIS
SEX OR THEY CAN BE A MNEMONIC
LABEL TST M1000M OR LABEL OR INSIDE A STRING

Note that in the LDA instruction the parameter "1000" is used as a number but in the LDB and TST instructions it is part of a label. The second parameter is not even substituted into the actual program, but rather into the comment field. The use of parameter number one in the TST instructions shows that the dummy parameter does not have to be a stand alone item but can be anywhere in the line.

Another convenient method of using macros is in conjunction with the IF-skip type of conditional directive. With a negative or backward skip we can cause a macro to loop back on itself during expansion. A good example of this would be a case where the programmer must initialize one hundred consecutive memory locations to the numbers one through one hundred. This would be a very tedious task if all these numbers had to be setup by FCB directives. Instead we can use a single FCB directive, the IF-skip type of directive, and the SET directive to accomplish this task.

INIT MACRO
COUNT SET 0 INITIALIZE COUNTER
COUNT SET COUNT+1 BUMP BY ONE
FCB COUNT SET THE MEMORY BYTE
IF COUNT<100,-2
ENDM

If you try this macro out, you will see that it expands into quite a bit of source if the macro expansions are being listed because the 3rd, 4th and 5th line are expanded for each of the one hundred times through. However, only one hundred bytes of object code are actually produced since lines 3 and 5 don't produce code.

This assembler does not allow local variables. If a label is specified on a line in the macro, you will receive a multiply defined symbol error if the macro is called more than once. There is a way to get around this shortcoming that is somewhat crude, but effective. That is to use a dummy parameter as a label and require the programmer to supply a different label name as that parameter each time the macro is called. For example, consider the following example:

SHIFT MACRO
PSHS D
LDA &l
LDB #&2
&3 ROLA
DECB
BNE &3
STA &l
PULS D
ENDM

Now if this macro was called with the line:

SHIFT FLAG,3,CALL1

The resulting macro expansion would look like:

PSHS D
LDA FLAG
LDB #3
CALL1 ROLA
DECB
BNE CALL1
STA FLAG
PULS D

There is no problem here, but if the macro were called again with the same string for parameter 3 (CALL1), a multiply defined symbol error would result. Thus any subsequent calls to SHIFT must have a unique string for parameter 3 such as CALL2, CALL3, etc.

IMPORTANT NOTES ON MACROS

  1. A macro must be defined before the first call to it.
  2. Macros can be nested both in calls and in definitions. That is, one macro may call another and one macro may be defined inside another.
  3. Comment lines are stripped out of macros when defined to save storage space in the macro text buffer.
  4. Local labels are not supported.
  5. A macro cannot call a LIBrary file. That is, a LIB directive cannot appear within a macro.
  6. No counting of parameters is done to be sure enough parameters are supplied on the calling line to satisfy all dummy parameters in the defined macro. If the body of a macro contains a dummy parameter for which no parameter is supplied on the calling line, the dummy parameter will be replaced with a null string or effectively removed.
  7. The macro name table is searched before the mnemonic table. This means that a standard mnemonic or directive can be effectively redefined by replacing it with a macro of the same name.
  8. Once a macro has been defined, it cannot be purged nor re-defined.


Table Of Contents Previous Next
Document Navigation