RISCOS.com

www.riscos.com Technical Support:
Acorn Assembler

 


Macros


Macros give you a means of placing a single instruction in your source which will be expanded at assembly time to several assembler instructions and directives, just as if you'd written those instructions and directives within the source at that point.

As an example, we will define a TestAndBranch instruction. This would normally take two ARM instructions. So we tell the Assembler, by means of a macro definition, that whenever it meets the TestAndBranch instruction, it is to insert the code we have given it in the macro definition. This is of course a convenience; we could just as easily write the relevant instructions out each time, but instead we let the Assembler do it for us.

The Assembler determines the destination of the branch with a macro parameter. This is a piece of information specified each time the macro is coded; the macro definition specifies how it is used. In the TestAndBranch example, we might also make the register to be tested a parameter, and even the condition to be tested for. Thus our macro definition might be:

        MACRO
$label  TestAndBranch $dest,$reg,$cc    ; This is called the macro prototype
                                        ; statement
$label  CMP     $reg,#0                 ; These two lines are the ones that
        B$cc    $dest                   ; will be substituted in the source.
        MEND                            ; This says the macro definition is
                                        ; finished

A use of the macro might be:

Test    TestAndBranch   NonZero,R0,NE
        ...
NonZero

The result, as far as the Assembler is concerned, is:

Test    CMP     R0,#0
        BNE     NonZero
        ...
NonZero

Syntax

The fact that a macro is about to be defined is given by the directive MACRO in the instruction field:

MACRO

This is immediately followed by a macro prototype statement which takes the form:

«$label» macroname «$parameter»«,$parameter»«,$parameter»...

«$label» If present, it is treated as an additional parameter.
«$parameter» Parameters are passed to the macro as strings and substituted before syntax analysis. Any number of them may be given.

The purpose of the macro prototype statement is to tell the Assembler the name of the macro being defined. The name of the macro is found in the opcode field of the macro prototype statement.

The macro prototype statement also tells the Assembler the names of the parameters, if any, of the macro. Parameters may occur in two places in the macro prototype statement. A single optional parameter may occur in the label field, shown as $label above. This is normally used if the macro expansion is to contain a program label, and is merely an aid to clarity, as can be seen in the TestAndBranch example. Any number of parameters, separated by commas, may occur in the operand field. All parameter names begin with the character $, to distinguish them from ordinary program labels.

The macro prototype statement can also tell the Assembler the default values of any of the parameters. This is done by following the parameter name by an equals sign, and then giving the default value. If the default value is to begin or end with a space then it should be placed within quotes. For example:

$reg    = R0
$string = " a string "

It is not possible to give a default value for the parameter in the label field.

For example:

        MACRO
$label  MACRONAME $num,$string,$etc
        .............
        .............
$label  ...lots of...
        .....code....
        = $num
        = $string
        = "the price is $etc"
        = 0
        MEND

  • MACRONAME is the name of this particular macro and $num, $string and $etc are its parameters. Other macros may have many more parameters, or even none at all.
  • The body of the macro follows after MACRONAME, with $label being optional even if it was given in the macro prototype statement.
  • $etc will be substituted into the string "the price is " when the macro is used.
  • The macro ends with MEND.

The macro is called by using its name and any missing parameters are indicated by commas, or may be omitted entirely if no more parameters are to follow. Thus, MACRONAME may be called in various ways:

MACRONAME        9,"disc",7

or:

MACRONAME        9

or:

MACRONAME        ,"disc",

Local variables

Local variables are similar to global variables, but may only be referenced within the macro expansion in which they were defined. They must be declared before they are used. The three types of local variable are arithmetic, logical and string. These are declared by:

Directive Local variable type Initial state
LCLA Arithmetic zero
LCLL Logical FALSE
LCLS String null string.

New values for local variables are assigned in precisely the same way as new variables for global variables: that is, using the directives SETA, SETL and SETS.

Syntax: variable_name SETx expression

Directive Local variable type
SETA Arithmetic
SETL Logical
SETS String

MEXIT directive

Normally, macro expansion terminates on encountering the MEND directive, at which point there must be no unclosed WHILE/WEND loops or pieces of conditional assembly. Early termination of a macro expansion can be forced by means of the MEXIT directive, and this may occur within WHILE/WEND loops and conditional assembly.

Default values

Macro parameters can be given default values at macro definition time, using the syntax:

$parameter=default_value

In the example of the macro MACRONAME already used:

        MACRO
$label  MACRONAME $num,$string,$etc
        .............
        .............
$label  ...lots of...
        .....code....
        = $num
        = $string
        = "the price is $etc"
        = 0
        MEND

you could instead write $num=10 in the macro prototype statement. Then, when calling the macro, a vertical bar character '|' will cause the default value 10 to be used rather than the value $num. For example:

        MACRONAME |,"disc",7

will be equivalent to:

        MACRONAME 10,"disc",7

Note that this default is not used when the macro argument is omitted - the value is then empty.

Macro substitution method

Each line of a macro is scanned so it can be built up in stages before being passed to the syntax analyser. The first stage is to substitute macro parameters throughout the macro and then to consider the variables. If string variables, logical variables and arithmetic variables are prefixed by the $ symbol, they are replaced by a string equivalent. Normal syntax checking is performed upon the line after these substitutions have been performed.

An important exception to these values is that vertical bar characters ('|') prevent substitution from taking place in some circumstances. To be specific, if a line contains vertical bars, substitution will be turned off after this first vertical bar, on again after the second one, off again after the third, and so on. This allows the use of dollar characters in symbols and labels (see the chapter entitled Symbols for details).

In certain circumstances, it may be necessary to prefix a macro parameter or variable to a label. In order to ensure that the Assembler can recognise the macro parameter or variable, it can be terminated by a dot '.' The dot will be removed during substitution.

For example:

        MACRO
$T33    MACRONAME
        .............
        .............
$T33.L25 ...lots of...
        .....code....
        MEND

If the dot had been omitted, the Assembler would not have related the $T33 part of the label to the macro statement and would have accepted $T33L25 as a label in its own right, which was not the intention.

Nesting macros

The body of a macro can contain a call to another macro; in other words, the expansion of one macro can contain references to macros. Macro invocation may be nested up to a depth of 255.

A division macro

As a final example, the following macro does an unsigned integer division:

; A macro to do unsigned integer division. It takes four parameters, each of
; which should be a register name:
;
; $Div: The macro places the quotient of the division in this register -
;       ie $Div := $Top DIV $Bot.
;       $Div may be omitted if only the remainder is wanted.
; $Top: The macro expects the dividend in this register on entry and places
;       the remainder in it on exit - ie $Top := $Top MOD $Bot.
; $Bot: The macro expects the divisor in this register on entry. It does not
;       alter this register.
; $Temp:The macro uses this register to hold intermediate results. Its
;       initial value is ignored and its final value is not useful.
;
; $Top, $Bot, $Temp and (if present) $Div must all be distinct registers.
; The macro does not check for division by zero; if there is a risk of this
; happening, it should be checked for outside the macro.

        MACRO
$Label  DivMod  $Div,$Top,$Bot,$Temp
        ASSERT  $Top <> $Bot            ; Produce an error if the
        ASSERT  $Top <> $Temp           ; registers supplied are
        ASSERT  $Bot <> $Temp           ; not all different.
        [       "$Div" /= ""
        ASSERT  $Div <> $Top
        ASSERT  $Div <> $Bot
        ASSERT  $Div <> $Temp
        ]

$Label  MOV     $Temp,$Bot              ; Put the divisor in $Temp
        CMP     $Temp,$Top,LSR #1       ; Then double it until
90      MOVLS   $Temp,$Temp,LSL #1      ; 2 * $Temp > $Top.
        CMP     $Temp,$Top,LSR #1
        BLS     %b90
        [       "$Div" /=""
        MOV     $Div,#0                 ; Initialise the quotient.
        ]

91      CMP     $Top,$Temp              ; Can we subtract $Temp?
        SUBCS   $Top,$Top,$Temp         ; If we can, do so.
        [       "$Div /= ""
        ADC     $Div,$Div,$Div          ; Double $Div & add new bit
        ]
        MOV     $Temp,$Temp,LSR #1      ; Halve $Temp,
        CMP     $Temp,$Bot              ; and loop until we've gone
        BHS     %b91                    ; past the original divisor.
        MEND

The statement:

Divide DivMod R0,R5,R4,R2

would be expanded to:

        ASSERT  R5 <> R4                ; Produce an error if the
        ASSERT  R5 <> R2                ; registers supplied are
        ASSERT  R4 <> R2                ; not all different
        ASSERT  R0 <> R5
        ASSERT  R0 <> R4
        ASSERT  R0 <> R2
Divide  MOV     R2,R4                   ; Put the divisor in R2.
        CMP     R2,R5,LSR #1            ; Then double it until
90      MOVLS   R2,R2,LSL #1            ; 2 * R2 > R5.
        CMP     R2,R5,LSR #1
        BLS     %b90
        MOV     R0,#0                   ; Initialise the quotient.
91      CMP     R5,R2                   ; Can we subtract R2?
        SUBCS   R5,R5,R2                ; If we can, do so.
        ADC     R0,R0,R0                ; Double R0 & add new bit.
        MOV     R2,R2,LSR #1            ; Halve R2,
        CMP     R2,R4                   ; and loop until we've gone
        BHS     %b91                    ; past the original divisor.

Similarly, the statement:

        DivMod  R6,R7,R8

would be expanded to:

        ASSERT  R6 <> R7                ; Produce an error if the
        ASSERT  R6 <> R8                ; registers supplied are
        ASSERT  R7 <> R8                ; not all different.
        MOV     R8,R7                   ; Put the divisor in R8.
        CMP     R8,R6,LSR #1            ; Then double it until
90      MOVLS   R8,R8,LSL #1            ; 2 * R8 > R6.
        CMP     R8,R6,LSR #1
        BLS     %b90
91      CMP     R6,R8                   ; Can we subtract R8?
        SUBCS   R6,R6,R8                ; If we can, do so.
        MOV     R8,R8,LSR #1            ; Halve R8,
        CMP     R8,R7                   ; and loop until we've gone
        BHS     %b91                    ; past the original divisor.

Note:

  • Conditional assembly is used to reduce the size of the assembled code (and increase its speed) in the case where only the remainder is wanted.
  • Local labels are used to avoid multiply defined labels if DivMod is used more than once in the assembler source.
  • The letter 'b' is used in the local label references (indicating that the Assembler should search backwards for the corresponding local labels) to ensure that the correct local labels are found.

This edition Copyright © 3QD Developments Ltd 2015
Last Edit: Tue,03 Nov 2015