http://www.tortall.net/projects/yasm/manual/html/nasm-multi-line-macros.html
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
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 Section 1.3.2).
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 [email protected]
, 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 Section 3.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.
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 Section 5.3.3 for a better way to write the above macro.
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 Section 4.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 Section 4.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 Section 4.3.6.
%0
: Macro Parameter CounterFor 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 Section 4.5) in order to iterate through all the parameters of a macro. Examples are given in Section 4.3.6.
%rotate
: Rotating Macro ParametersUnix 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.
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 (Section 4.3.2) and context-local labels (Section 4.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.)
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 Section 4.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.
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