3.3. Multi-Line Macros

Multi-line macros are much more like the type of macro seen in MASM and TASM: a multi-line macro definition in NASM looks something like this.

%macro  prologue 1

        push    ebp
        mov     ebp,esp
        sub     esp,%1

%endmacro

This defines a C-like function prologue as a macro: so you would invoke the macro with a call such as

myfunc:   prologue 12

which would expand to the three lines of code

myfunc: push    ebp
        mov     ebp,esp
        sub     esp,12

The number 1 after the macro name in the %macro line defines the number of parameters the macro prologue expects to receive. The use of %1 inside the macro definition refers to the first parameter to the macro call. With a macro taking more than one parameter, subsequent parameters would be referred to as %2, %3 and so on.

Multi-line macros, like single-line macros, are case-sensitive, unless you define them using the alternative directive %imacro.

If you need to pass a comma as part of a parameter to a multi-line macro, you can do that by enclosing the entire parameter in braces. So you could code things like

%macro  silly 2

    %2: db      %1

%endmacro

        silly 'a', letter_a             ; letter_a:  db 'a'
        silly 'ab', string_ab           ; string_ab: db 'ab'
        silly {13,10}, crlf             ; crlf:      db 13,10

3.3.1. Overloading Multi-Line Macros

As with single-line macros, multi-line macros can be overloaded by defining the same macro name several times with different numbers of parameters. This time, no exception is made for macros with no parameters at all. So you could define

%macro  prologue 0

        push    ebp
        mov     ebp,esp

%endmacro

to define an alternative form of the function prologue which allocates no local stack space.

Sometimes, however, you might want to «overload» a machine instruction; for example, you might want to define

%macro  push 2

        push    %1
        push    %2

%endmacro

so that you could code

        push    ebx             ; this line is not a macro call
        push    eax,ecx         ; but this one is

Ordinarily, NASM will give a warning for the first of the above two lines, since push is now defined to be a macro, and is being invoked with a number of parameters for which no definition has been given. The correct code will still be generated, but the assembler will give a warning. This warning can be disabled by the use of the -wno-macro-params command-line option (see Раздел 1.3.2).

3.3.2. Macro-Local Labels

NASM allows you to define labels within a multi-line macro definition in such a way as to make them local to the macro call: so calling the same macro multiple times will use a different label each time. You do this by prefixing %% to the label name. So you can invent an instruction which executes a RET if the Z flag is set by doing this:

%macro  retz 0

        jnz     %%skip
        ret
    %%skip:

%endmacro

You can call this macro as many times as you want, and every time you call it NASM will make up a different «real» name to substitute for the label %%skip. The names NASM invents are of the form ..@2345.skip, where the number 2345 changes with every macro call. The ..@ prefix prevents macro-local labels from interfering with the local label mechanism, as described in Раздел 2.9. You should avoid defining your own labels in this form (the ..@ prefix, then a number, then another period) in case they interfere with macro-local labels.

3.3.3. Greedy Macro Parameters

Occasionally it is useful to define a macro which lumps its entire command line into one parameter definition, possibly after extracting one or two smaller parameters from the front. An example might be a macro to write a text string to a file in MS-DOS, where you might want to be able to write

        writefile [filehandle],"hello, world",13,10

NASM allows you to define the last parameter of a macro to be greedy, meaning that if you invoke the macro with more parameters than it expects, all the spare parameters get lumped into the last defined one along with the separating commas. So if you code:

%macro  writefile 2+

        jmp     %%endstr
  %%str:        db      %2
  %%endstr:
        mov     dx,%%str
        mov     cx,%%endstr-%%str
        mov     bx,%1
        mov     ah,0x40
        int     0x21

%endmacro

then the example call to writefile above will work as expected: the text before the first comma, [filehandle], is used as the first macro parameter and expanded when %1 is referred to, and all the subsequent text is lumped into %2 and placed after the db.

The greedy nature of the macro is indicated to NASM by the use of the + sign after the parameter count on the %macro line.

If you define a greedy macro, you are effectively telling NASM how it should expand the macro given any number of parameters from the actual number specified up to infinity; in this case, for example, NASM now knows what to do when it sees a call to writefile with 2, 3, 4 or more parameters. NASM will take this into account when overloading macros, and will not allow you to define another form of writefile taking 4 parameters (for example).

Of course, the above macro could have been implemented as a non-greedy macro, in which case the call to it would have had to look like

        writefile [filehandle], {"hello, world",13,10}

NASM provides both mechanisms for putting ((commas in macro parameters)), and you choose which one you prefer for each macro definition.

See Раздел 4.3.3 for a better way to write the above macro.

3.3.4. Default Macro Parameters

NASM also allows you to define a multi-line macro with a range of allowable parameter counts. If you do this, you can specify defaults for omitted parameters. So, for example:

%macro  die 0-1 "Painful program death has occurred."

        writefile 2,%1
        mov     ax,0x4c01
        int     0x21

%endmacro

This macro (which makes use of the writefile macro defined in Раздел 3.3.3) can be called with an explicit error message, which it will display on the error output stream before exiting, or it can be called with no parameters, in which case it will use the default error message supplied in the macro definition.

In general, you supply a minimum and maximum number of parameters for a macro of this type; the minimum number of parameters are then required in the macro call, and then you provide defaults for the optional ones. So if a macro definition began with the line

%macro foobar 1-3 eax,[ebx+2]

then it could be called with between one and three parameters, and %1 would always be taken from the macro call. %2, if not specified by the macro call, would default to eax, and %3 if not specified would default to [ebx+2].

You may omit parameter defaults from the macro definition, in which case the parameter default is taken to be blank. This can be useful for macros which can take a variable number of parameters, since the %0 token (see Раздел 3.3.5) allows you to determine how many parameters were really passed to the macro call.

This defaulting mechanism can be combined with the greedy-parameter mechanism; so the die macro above could be made more powerful, and more useful, by changing the first line of the definition to

%macro die 0-1+ "Painful program death has occurred.",13,10

The maximum parameter count can be infinite, denoted by *. In this case, of course, it is impossible to provide a full set of default parameters. Examples of this usage are shown in Раздел 3.3.6.

3.3.5. %0: Macro Parameter Counter

For a macro which can take a variable number of parameters, the parameter reference %0 will return a numeric constant giving the number of parameters passed to the macro. This can be used as an argument to %rep (see Раздел 3.5) in order to iterate through all the parameters of a macro. Examples are given in Раздел 3.3.6.

3.3.6. %rotate: Rotating Macro Parameters

Unix shell programmers will be familiar with the shift shell command, which allows the arguments passed to a shell script (referenced as $1, $2 and so on) to be moved left by one place, so that the argument previously referenced as $2 becomes available as $1, and the argument previously referenced as $1 is no longer available at all.

NASM provides a similar mechanism, in the form of %rotate. As its name suggests, it differs from the Unix shift in that no parameters are lost: parameters rotated off the left end of the argument list reappear on the right, and vice versa.

%rotate is invoked with a single numeric argument (which may be an expression). The macro parameters are rotated to the left by that many places. If the argument to %rotate is negative, the macro parameters are rotated to the right.

So a pair of macros to save and restore a set of registers might work as follows:

%macro  multipush 1-*

  %rep  %0
        push    %1
  %rotate 1
  %endrep

%endmacro

This macro invokes the PUSH instruction on each of its arguments in turn, from left to right. It begins by pushing its first argument, %1, then invokes %rotate to move all the arguments one place to the left, so that the original second argument is now available as %1. Repeating this procedure as many times as there were arguments (achieved by supplying %0 as the argument to %rep) causes each argument in turn to be pushed.

Note also the use of * as the maximum parameter count, indicating that there is no upper limit on the number of parameters you may supply to the multipush macro.

It would be convenient, when using this macro, to have a POP equivalent, which didn’t require the arguments to be given in reverse order. Ideally, you would write the multipush macro call, then cut-and-paste the line to where the pop needed to be done, and change the name of the called macro to multipop, and the macro would take care of popping the registers in the opposite order from the one in which they were pushed.

This can be done by the following definition:

%macro  multipop 1-*

  %rep %0
  %rotate -1
        pop     %1
  %endrep

%endmacro

This macro begins by rotating its arguments one place to the right, so that the original last argument appears as %1. This is then popped, and the arguments are rotated right again, so the second-to-last argument becomes %1. Thus the arguments are iterated through in reverse order.

3.3.7. Concatenating Macro Parameters

NASM can concatenate macro parameters on to other text surrounding them. This allows you to declare a family of symbols, for example, in a macro definition. If, for example, you wanted to generate a table of key codes along with offsets into the table, you could code something like

%macro keytab_entry 2

    keypos%1    equ     $-keytab
                db      %2

%endmacro

keytab:
          keytab_entry F1,128+1
          keytab_entry F2,128+2
          keytab_entry Return,13

which would expand to

keytab:
keyposF1        equ     $-keytab
                db     128+1
keyposF2        equ     $-keytab
                db      128+2
keyposReturn    equ     $-keytab
                db      13

You can just as easily concatenate text on to the other end of a macro parameter, by writing %1foo.

If you need to append a digit to a macro parameter, for example defining labels foo1 and foo2 when passed the parameter foo, you can’t code %11 because that would be taken as the eleventh macro parameter. Instead, you must code %{1}1, which will separate the first 1 (giving the number of the macro parameter) from the second (literal text to be concatenated to the parameter).

This concatenation can also be applied to other preprocessor in-line objects, such as macro-local labels (Раздел 3.3.2) and context-local labels (Раздел 3.7.2). In all cases, ambiguities in syntax can be resolved by enclosing everything after the % sign and before the literal text in braces: so %{%foo}bar concatenates the text bar to the end of the real name of the macro-local label %%foo. (This is unnecessary, since the form NASM uses for the real names of macro-local labels means that the two usages %{%foo}bar and %%foobar would both expand to the same thing anyway; nevertheless, the capability is there.)

3.3.8. Condition Codes as Macro Parameters

NASM can give special treatment to a macro parameter which contains a condition code. For a start, you can refer to the macro parameter %1 by means of the alternative syntax %+1, which informs NASM that this macro parameter is supposed to contain a condition code, and will cause the preprocessor to report an error message if the macro is called with a parameter which is not a valid condition code.

Far more usefully, though, you can refer to the macro parameter by means of %-1, which NASM will expand as the inverse condition code. So the retz macro defined in Раздел 3.3.2 can be replaced by a general conditional-return macro like this:

%macro  retc 1

        j%-1    %%skip
        ret
  %%skip:

%endmacro

This macro can now be invoked using calls like retc ne, which will cause the conditional-jump instruction in the macro expansion to come out as JE, or retc po which will make the jump a JPE.

The %+1 macro-parameter reference is quite happy to interpret the arguments CXZ and ECXZ as valid condition codes; however, %-1 will report an error if passed either of these, because no inverse condition code exists.

3.3.9. Disabling Listing Expansion

When NASM is generating a listing file from your program, it will generally expand multi-line macros by means of writing the macro call and then listing each line of the expansion. This allows you to see which instructions in the macro expansion are generating what code; however, for some macros this clutters the listing up unnecessarily.

NASM therefore provides the .nolist qualifier, which you can include in a macro definition to inhibit the expansion of the macro in the listing file. The .nolist qualifier comes directly after the number of parameters, like this:

%macro foo 1.nolist

Or like this:

%macro bar 1-5+.nolist a,b,c,d,e,f,g,h