Macros
If a stack based calling convention is used, then a number of registers may frequently need to be pushed and popped from the stack during calls and returns. In order to push ARC register %r15 onto the stack, we need to first decrement the stack pointer (which is in %r14) and then copy %r15 to the memory location pointed to by %r14 as shown in the code below:
The compact form assigns a new label (push) to the sequence of statements that actually carry out the command. The push label is referred to as a macro, and the process of translating a macro into its assembly language equivalent is referred to as macro expansion.
A macro can be created through the use of a macro definition, as shown for push in Figure 5-9. The macro begins with a .macro pseudo-op, and termi-
nates with a .endmacro pseudo-op. On the .macro line, the first symbol is the name of the macro (push here), and the remaining symbols are command line arguments that are used within the macro. There is only one argument for macro push, which is arg1. This corresponds to %r15 in the statement “push %r15,” or to %r1 in the statement “push %r1,” etc. The argument (%r15 or %r1) for each case is said to be “bound” to arg1 during the assembly process.
Additional formal parameters can be used, separated by commas as in:
The body of the macro follows the .macro pseudo-op. Any commands can fol- low, including other macros, or even calls to the same macro, which allows for a recursive expansion at assembly time. The parameters that appear in the .macro line can replace any text within the macro body, and so they can be used for labels, instructions, or operands.
It should be noted that during macro expansion formal parameters are replaced by actual parameters using a simple textual substitution. Thus one can invoke the push macro with either memory or register arguments:
The programmer needs to be aware of this feature of macro expansion when the macro is defined, lest the expanded macro contain illegal statements.
Additional pseudo-ops are needed for recursive macro expansion. The .if and .endif pseudo-ops open and close a conditional assembly section, respectively.
If the argument to .if is true (at macro expansion time) then the code that follows, up to the corresponding .endif, is assembled. If the argument to .if is false, then the code between .if and .endif is ignored by the assembler. The conditional operator for the .if pseudo-op can be any member of the set {<, =,.
Figure 5-10 shows a recursive macro definition and its expansion during the assembly process. The expanded code sums the contents of registers %r1 through %rX and places the result in %r1. The argument X is tested in the .if line. If X is greater than 2, then the macro is called again, but with the argument X – 1. If the macro recurs_add is invoked with an argument of 4, then three lines of
code are generated as shown in the bottom of the figure. The first time that recurs_add is invoked, X has a value of 4. The macro is invoked again with X = 3 and X = 2, at which point the first addcc statement is generated. The second and third addcc statements are then generated as the recursion unwinds.
As mentioned earlier, for an assembler that supports macros, there must be a macro expansion phase that takes place prior to the two-pass assembly process. Macro expansion is normally performed by a macro preprocessor before the program is assembled. The macro expansion process may be invisible to a programmer, however, since it may be invoked by the assembler itself. Macro expansion typically requires two passes, in which the first pass records macro definitions, and the second pass generates assembly language statements. The second pass of macro expansion can be very involved, however, if recursive macro definitions are supported. A more detailed description of macro expansion can be found in (Donovan, 1972).