Macros, Part 1

Overview

The term macro is used in several places. In the C and C++ languages, we can define macros using #define. MS Word and Notepad++ allow us to define keystroke macros (record a sequence of keystrokes and play it back repeatedly).

In our assembly language, a macro is a package of code (outside of any CSECT) including spcial macro instructions as well as ordinary instructions. It has a name, and when we use it, we provide values for it to use. A macro preprocessor then uses the macro code and the provided values to generate actual code (not necessarily always the same code) for us.


Format of a macro

The skeleton of a macro looks like this:

          MACRO
&LABEL    NAME   list of 0 or more symbolic parameters,separated by commas
          ... assorted code
          MEND

Here a symbolic parameter is a placeholder. It has a name which:

&LABEL is an ordinary label which we supply when we call the macro. It is normally put on the first line of executable code. It is optional and is often omitted.

MEND is the last line in the delimiter. Don't forget it.

We list macros in our file befor ethe first CSECT. It is also possible to create macro libraries and provide them to the assembler using JCL, but that is beyond the scope of this course.

When we use a macro, it looks something like this:

label     NAME  list of actual values

Here &LABEL will have the value "label" and the actual values are matched with the symbolic parameters, one at a time, left to right. The macro preprocessor goes through the macro code and replaces each occurrence of a symbolic parameter with the corresponding actual value.

The value of a symbolic parameter is simply a sequence of characters (a string). If one of the actual values is missing, the symbolic parameter's value will be an empty string.

The actual values are separated by commas. If we have two commas in a row, this is treated as having an empty string between them. Thus if we have:

          MYMACRO   ABC,,23

then the first symbolic parameter's value is "ABC", the second's value is an empty string, and the third's value is "23" (not the number 23).


A simple example

Here is a macro to set &B = absolute value of &A. The symbolic parameters are &A and &B, and their values should be the labels or D(B) addresses of fullwords.

         MACRO
&LABEL   ABS   &A, &B
&LABEL   ST    5,*+8
         B     *+8
         DS    F
         L     5,&B
         LPR   5,5
         ST    5,&A 
         L     5,*-14
         MEND

If a programmer uses this:

          ABS   F,G

then the code generated will look like this:

+         ST   5,*+8
+         B    *+8
+         DS   F
+         L    5,&B
+         LPR  5,5
+         ST   5,&A
+          L    5,*-14
          MEND

The lines produced by expanding the macro are marked with '+' in column 1 in the listing.

There are problems with this macro. One is the use of *+8 and *-14. Many people are not comfortable with this. It would be easier to read if we had a label for the fullword. Unfortunately, the same label would be generated every time we used the macro, and it is illegal to have duplicate labels.

Another problem is that a programmer might forget to provide one of the values:

          ABS   F

In this case, &B will have an empty string as its value and we will have assembler errors from that.

Thus we need a way to generate unique labels, and we need a way to check on missing arguments.


Check on missing arguments

If we use a macro and omit one of the values, the corresponding symbolic parameter will have an empty string as its value. How can we detect this?

The tool we need is AIF, one of the special macro instructions. Its format looks like this:

     AIF (logical condition) .label

Here a logical condition may be:

For comparisons, we use operators:

(If you are wondering why we don't use more modern symbols such as "==" and "!=", the answer is probably that when this assembly language was invented, keyboards did not include all the symbols we now have.)

The .label is a label (distinguished by the initial period) which exists only inside the macro code itself. It should be put on a special macro instruction, not on ordinary code. It will not appear in the program listing.

Suppose we identify a missing argument. What can we do about it? We can use MNOTE, one of the special macro instructions. It will look like this:

     MNOTE 'message'

The message is printed in the program listing. It is not part of the output from running the program.


Creating unique labels

We want to be able to use labels in our macros, but the labels have to be different each time the macro is called. To do this, we need &SYSNDX, a "system variable". Its value is initially 0001, and it is incremented by 1 after each call to a macro that mentions it.

We typically use &SYSNDX by concatenating it with another value. For instance, we might have M&SYSNDX. When the macro is first called, this is M0001, and when the macro is next called, it will be M0002, and so on.

Thus we could generate unique labels in up to 9999 macro calls. (If your program needs more macro calls than that, don't ask your instructor to debug it.)


A few more special macro instructions and trivia

A variation on AIF is AGO. It is a way to have an unconditional branch in macro processing. For instance:

     AGO .DONE

which is equivalent to

     AIF (1) .DONE

Sometimes it is useful to be able to end macro processing somewhere short of MEND. For this we have MEXIT. If it is encountered in macro processing, the processing of the macro stops at that point.

A special macro instruction which is sometimes useful is ANOP, which does nothing.

We can have comments in a macro which are not reproduced in the program listing. These are marked with ".*" in columns 1 and 2. The entire line after that will be a comment.

If we call a macro repeatedly in a program, we may find it a nuisance to have a lot of generated code inserted into our program listing. We can control this with assembler instructions PRINT GEN and PRINT NOGEN.

PRINT GEN means "start including all the generated code in the listing", and PRINT NOGEN means "stop including the generated code".

We can switch this on and off repeatedly. Put these in column 10. They do not generate any object code; they are simply telling the assembler how to do its work.


Simple example revisited

         MACRO
.*       This is a macro to find the absolute value of a fullword.
.*       It will set &A = the absolute value of &B.
.*       The values of &A and &B should be labels or addresses of 
.*       fullwords.
&LABEL   ABS   &A, &B
         AIF ('&A' EQ '').ERROR
         AIF ('&B' EQ '').ERROR
&LABEL   ST    5,L&SYSNDX
         B     M&SYSNDX
L&SYSNDX DS    F
M&SYSNDX L     5,&B
         LPR   5,5
         ST    5,&A 
         L     5,L&SYSDX
         MEXIT
.ERROR   MNOTE 'Missing argument'
         MEND

Here we are using &SYSNDX to create two unique labels, and as we do not want the error message printed unless there is an error, we use MEXIT.

The last few lines could have been written as:

         L     5,L&SYSDX
         AGO .DONE
.ERROR   MNOTE 'Missing argument'
.DONE    ANOP
         MEND

which branches around the MNOTE line instead of using MEXIT.

If a macro has a large number of symbolic parameters, we may find that we cannot fit all of them on one line. The same problem can occur with ordinary instructions. How can we continue an instruction to more than one line?

To do this:

It is possible to extend an instruction or a macro call over several lines, and some people find this easier to read.