CS222 Lecture: Macros and Conditional Assembly 10/30/92
revised 2/16/96
Materials: Handout showing "faking" of new VAX opcodes via Macros
Introduction
------------
A. After writing a few MACRO programs, you realize they tend to be long and
may involve a lot of repetitious code that is tedious to write.
Example: In one of the labs, you were required to check for overflow after
various arithmetic operations. Suppose that, instead of
terminating processing of the equation when an overflow
occurred, you were asked to simply write a warning message
and keep going. Suppose further that we would be hapy using
the same message in each case. The code to do this might look
like this:
BVC 1$
MOVAB O_MESSAGE, R0
MOVL #O_MESSAGE_LEN, R1
JSB PLINE
1$:
This same block of code would need to be incorporated about 4 times
in your program.
B. To reduce the repetitious work, many assemblers (including ours) contain
a macro facility that allows a programmer to define a block of code to
be inserted into a program whenever needed by a simple command.
Example: The above could be handled as follows:
1. Define a macro
.MACRO OCHECK, ?NONE
BVC NONE
MOVAB O_MESSAGE, R0
MOVL #O_MESSAGE_LEN, R1
JSB PLINE
NONE:
.ENDM OCHECK
2. Each time the code is needed, it could be included by:
OCHECK
This will cause the assembler to include all the code between
the .MACRO and .ENDM directives.
3. We will now look at the macro facility in VAX MACRO in detail, followed
by a feature often used in conjunction with it called CONDITIONAL
ASSEMBLY.
I. The Macro Facility in VAX MACRO
- --- ----- -------- -- --- -----
A. The macro facility allows a progam to contain MACRO DEFINITIONS and
MACRO CALLS
1. A macro definition begins with a .MACRO directive, and ends with a
.ENDM directive. The code that lies between these directives
constitutes the BODY of the macro.
2. The .MACRO directive gives the macro a name. When this name is
encountered in the op-code position on a subsequent line of the
source program, it is considered a macro call, and the macro body is
substituted for it. (This process is called MACRO EXPANSION)
B. The macro facility, while included in the assembler, is conceptually a
distinct facility from the assembler itself. We can think of it as
being done by a PRE-PROCESSOR that manipulates the source program
text before the assembler proper sees it:
_________ _____________ -------------
/ Source / | Pre- | | Assembler |
( File (------->| processor |------->| proper |
\________\ |___________| |___________|
^
|
v
______________
| macro |
| definition |
| table |
|____________|
1. Ordinarily, the pre-processor simply passes source text through to
the assembler proper unchanged.
On diagram: --------------->
2. However, when the pre-processor encounters a .MACRO directive, it
stores the text that follows (up to the terminating .ENDM directive)
in the macro definition table. This text is NOT passed through to
the assembler proper.
On diagram: ---------
|
v
3. When the pre-processor encounters the name of a defined macro, it
feeds the body of the macro to the assembler processor, just as if
it had come from the source file.
On diagram: ------->
|
^
4. Note that the body of a macro is not processed by the assembler until
the macro is called.
a. Thus, if the macro body contains syntax errors, they will not be
detected until the macro is called.
b. We will see that macro bodies can contain directives, including
other macro definitions. However, these are not processed either
until the macro is called.
C. Contrast macros with procedures:
1. Procedure: one physical copy is assembled, which may be called from
many points in the program, at run-time.
2. Macro: a separate physical copy is assembled each time the macro is
called, at assembly-time.
3. Efficiency consequences:
a. Procedures are generally more space efficient. (Only one physical
copy exists.)
b. Macros are generally more time efficient. (There is no run-time
overhead for calling.)
D. To make macros more flexible, they can have ARGUMENTS: formal arguments
in the definition, and actual arguments in the call.
1. Example: A macro to perform the mod operation
.MACRO MODL A, B
DIVL3 A, B, -(SP) ; Top of stack := B div A
MULL2 A, (SP) ; Top of stack := A * (B div A)
SUBL2 (SP)+, B ; B := B - A * (B div A) = B mod A
.ENDM MODL
This might be called by something like:
MODL #10, R0 ; Compute R0 mod 10
a. In the definition, A and B are FORMAL ARGUMENTS.
b. In the call, #10 and R0 are ACTUAL ARGUMENTS.
c. The code that the assembler will assemble is the same as if the
programmer had written:
DIVL3 #10, R0, -(SP)
MULL2 #10, (SP)
SUBL2 (SP)+, R0
d. Normally, a macro call will have as many actual arguments as the
definition has formal arguments.
i. It is permissible for the call to have fewer arguments - in
which case the extra arguments are blank (unless a default is
specified, as described below.)
ii. It is an error for a macro call to specify more actual arguments
than the definition has formal arguments.
2. Note that the processing of macro arguments is a TEXTUAL operation.
The TEXT of the actual argument is substituted for the text of the
corresponding formal argument wherever it occurs.
3. Some additional facilities available for use with macro arguments
a. The concatenation operator - '
Example: Instead of creating a MACRO for MODL, we could create
a generic macro that works for any data type for which
multiplication and division is defined, as follows:
.MACRO MOD2 TYPE, A, B
DIV'TYPE'3 A, B, -(SP) ; Top of stack := B div A
MUL'TYPE'2 A, (SP) ; Top of stack := A * (B div A)
SUB'TYPE'2 (SP)+, B ; B := B - A * (B div A) = B mod A
.ENDM MOD2
If this were called by
MOD2 L, #10, R0
The generated code would be:
DIVL3 #10, R0, -(SP)
MULL2 #10, (SP)
SUBL2 (SP)+, R0
While if it were called by
MOD2 B, #10, R0
The generated code would be:
DIVB3 #10, R0, -(SP)
MULB2 #10, (SP)
SUBB2 (SP)+, R0
In fact, we could get really fancy and define further macros to
give the appearance that the VAX has a MODx machine instruction
even though it doesn't:
.MACRO MODB2 X,Y
MOD2 B,X,Y
.ENDM MODB2
-- MODW2, MODL2 done similarly
b. The use of non-positional syntax for actual arguments
Example: Given the above definition for MOD, all of the following
calls are equivalent:
MOD L, #10, R0
MOD TYPE=L, A=#10, B=R0
MOV TYPE=L, A=R0, B=#10
MOD A=#10, B=R0, TYPE=L
(plus several other possibilities)
c. The specification of default values for arguments, allowing an
actual argument to be omitted
Example: The following macro swaps two longword arguments.
This requires a temporary location, which can be either
specified by the programmer, or R0 will be used by
default
.MACRO SWAPL A, B, TEMP=R0
MOVL A, TEMP
MOVL B, A
MOVL TEMP, B
.ENDM SWAPL
If this were called by
SWAPL X, Y, R7
The generated code would be
MOVL X, R7
MOVL Y, X
MOVL R7, Y
However, if it were called by
SWAPL X, Y
The generated code would be
MOVL X, R0
MOVL Y, X
MOVL R0, Y
i. When a macro is called with positional syntax, only the last
argument(s) can meaningfully have defaults
ii. If a macro is going to be called with non-positional syntax,
any argument can have a default and can be omitted.
d. If an actual argument contains spaces or punctuation marks,
it may be necessary to enclose it in angle brackets.
Example:
.MACRO XYZ ARGS
ADDL3 ARGS
.ENDM XYZ
The programmer may wish to call this by:
XYZ R0, R1, R2
Intentending the generated code to be:
ADDL3 R0, R1, R2
However, R0 would be taken as the match for ARGS, and R1 and R2
would be regarded as extra arguments - an error.
The call has to be written this way:
XYZ <R0, R1, R2>
This would generate the intended code.
E. Generating unique labels
1. A common problem in writing macros is creating appropriate labels
within the macro body without duplication.
Example: Suppose we want to define a macro that adds two numbers and
branches to an error handler if an overflow occurs. We could
write something like this:
.MACRO ADD_CHECKED A, B
ADDL2 A, B
BVC NO_OVER
JMP OVERFLOW_ERROR
NO_OVER:
.ENDM ADD_CHECKED
If this macro were called only once, we would have no problem; but if
it were used two or more times (presumably the reason for defining it
in the first place), then the symbol NO_OVER would be multiply defined.
2. To address this problem, VAX MACRO has a facility that automatically
generates unique labels - a different one each time the macro is
called. Our macro would be defined this way:
.MACRO ADD_CHECKED A, B, ?NO_OVER
ADDL2 A, B
BVC NO_OVER
JMP OVERFLOW_ERROR
NO_OVER:
.ENDM ADD_CHECKED
a. If the macro is called with no third argument, a unique label
is automatically generated. These will be of the form
30001$, 30002$ ...
Example: Suppose we have the following series of calls:
ADD_CHECKED X, R0
ADD_CHECKED Y, R0
The generated code might be
ADDL2 X, R0
BVC 30001$
JMP OVERFLOW_ERROR
30001$:
ADDL2 Y, R0
BVC 30002$
JMP OVERFLOW_ERROR
30002$:
b. If the user wants to, he can specify a label to be used by
specifying an explicit third argument.
Example: The following call
ADD_CHECKED R1, R2, OK
Expands to:
ADDL2 R1, R2
BVC OK
JMP OVERFLOW_ERROR
OK:
F. In addition to creating his own macros, a programmer can also use
macros from a library of predefined macros.
1. There is a library of predefined macros for accessing various system
routines. The names of these macros all begin with $.
Example: the macro $EXIT_S calls the exit program system service. It
takes an optional argument specifying the final status code
for the program.
2. Other macro libraries can be incorporated in an assembly process by
using a /LIBRARY qualifier on the command line or a .LIBRARY directive
in the program.
G. Macro definitions and calls can be NESTED:
1. A macro definition can contain a call to another macro
Example - a macro that checks the status value returned by a system
routine and exits the program if it is a failure status:
.MACRO CHECK WHAT=R0, ?OK
BLBS WHAT, OK
$EXIT_S WHAT
OK:
.ENDM CHECK
As we just noted, $EXIT_S is, in fact, a system macro which will be
called as part of the process of expanding CHECK.
2. A macro definition can contain a definition of another macro. In
this case, the nested definition is not processed until the outer
macro is called.
Example: We saw earlier that we could use macros to create the
appearance of new VAX machine instructions - e.g. MODB2,
MODL2, MODW2. Of course, we could also create MODB3 etc,
and we could also create other operations like AND (which the
VAX does not have).
We could automate much of the work as follows:
a. Define "generic" MOD2, MOD3, AND2, AND3 etc. macros that take a
type as a parameter.
b. Define two more "constructor" macros:
.MACRO FAKE_OP NAME, TYPE
.MACRO NAME'TYPE'2 X, Y
NAME'2 TYPE, X, Y
.ENDM NAME'TYPE'2
.MACRO NAME'TYPE'3 X, Y, Z
NAME'3 TYPE, X, Y, Z
.ENDM NAME'TYPE'3
.ENDM FAKE_OP
.MACRO DO_FAKES NAME
FAKE_OP NAME, B
FAKE_OP NAME, W
FAKE_OP NAME, L
.ENDM DO_FAKES
c. Now we could create six versions of a macro like MOD (given
definitions for MOD2 and MOD3) by
DO_FAKES MOD
HANDOUT
II. Conditional and Repeat Assembly
-- ----------- --- ------ --------
A. The conditional and repeat assembly facility of VAX MACRO allows the
programmer to "program" the assembler to generate certain code
conditionally, or repeatedly.
1. Example: Suppose a certain program contains debugging code, which
should be included when the program is assembled during
the software development process, but not when the "production"
version is assembled. This can be handled as follows:
a. Include at the beginning of the program one of the following lines
DEBUG=1 ; if debugging code should be included
DEBUG=0 ; if not
b. Enclose each block of debugging code by:
.IF NE DEBUG
...
.ENDC
The enclosed code will be assembled if DEBUG is 1, and will be
ignored if DEBUG is 0.
2. Example: Suppose one wishes to initialize to all -1's an array of 40
longwords. In terms of execution time, the fastest way to do this
to use a series of 10 MOVO instructions, each of which handles 4
of them. (This is much faster than a loop.) This could be handled
by writing:
MOVAL MY_ARRAY, R0
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
However, the programmer could save the effort of writing the MOVO
instruction 10 times by instead writing:
MOVAL MY_ARRAY, R0
.REPEAT 10
MOVO #-1, (R0)+
.ENDR
3. Note that these conditional and repeat operations are done by the
assembler AT ASSEMBLY TIME. In essence, they constitute a program
EXECUTED by the assembler to GENERATE code to be assembled.
B. This facility is logically distinct from the macro facility we just
looked at, but is usually studied with it for two reasons:
1. The same preprocessor handles both macro definition / expansion and
conditional and repeat assembly.
2. One of the major uses of conditional assembly is in writing flexible
macros that can be expanded in different ways to suit different
circumstances.
Example: The SWAPL macro we defined earlier uses R0 as a temporary
if the programmer specifies none. It would be safer to
save R0 on the stack before using it. This cannot be
handled by simply using the default TEMP=R0.
.MACRO SWAPL A, B, TEMP
.IF BLANK TEMP
PUSHL R0
MOVL A, R0
MOVL B, A
MOVL R0, B
MOVL (SP)+, R0
.IF_FALSE
MOVL A, TEMP
MOVL B, A
MOVL TEMP, B
.ENDC
.ENDM SWAPL
C. In general, a conditional assembly block consists of:
1. An initial .IF directive to specify the condition to be tested
a. One form of condition compares an ASSEMBLY-TIME expression to 0
EQUAL (or EQ) expression - true iff expression = 0
NOT_EQUAL (or NE) expression - true iff expression <> 0
GREATER (or GT) expression - true iff expression > 0
LESS_THAN (or LT) expression - true iff expression < 0
GREATER_EQUAL (or GE) expression- true iff expression >= 0
LESS_EQUAL (or LE) expression - true iff expression <= 0
Example: assemble code iff the symbol DEBUG has non-zero value
.IF NE DEBUG
b. Another form tests to see whether or not a given symbol has been
defined
DEFINED (or DF) symbol
NOT_DEFINED (or NDF) symbol
Example: Give the symbol BUFFLEN a default value of 80 if no
value has been assigned
.IF NDF BUFFLEN
BUFFLEN=80
.ENDC
c. Another form tests macro actual arguments to see if they are blank
BLANK (or B) formal_argument - true iff argument is blank
(no actual specified)
NOT_BLANK (or NB) formal_argument- true iff argument is nonblank
(a non-blank actual was
specified)
Example: See above
d. Another form tests to see if an actual macro argument is the same
as some specified text
IDENTICAL (or IDN) formal_argument text
DIFFERENT (or DIF) formal_argument text
Example: The SWAPL macro we defined would fail if either A or B
were R0. To achieve more complete generality (at the cost
of incredible complexity), we could include tests like
.IF IDENTICAL A, R0
...
2. Any number of lines of code, optionally interspersed with any number of
subconditional directives
a. The directive .IF_FALSE (or .IFF) causes the code that follows it
to be assembled if the main condition was FALSE. (Its effect is
like that of ELSE, but takes place at assembly time.)
Example: SWAPL above
b. The directive .IF_TRUE (or .IFT) can be inserted after a .IF_FALSE
to resume assembling only if the main condition was TRUE. (There
is no analogue in conventional programming.)
c. The directive .IF_TRUE_FALSE (or .IFTF) can be inserted to to
cause code in the middle of a conditional block to always be
assembled.
Example:
.IF NE DEBUG
-- this code assembled only if DEBUG is non-zero
.IFF
-- this code assembled only if DEBUG is zero
.IFT
-- this code assembled only if DEBUG is non-zero
.IFTF
-- This code always assembled
.IFT
-- this code assembled only if DEBUG is non-zero
.IFF
-- this code assembled only if DEBUG is zero
.ENDC
3. A terminating .ENDC directive
4. If the body of a conditional assembly block would consist of only a
single line, a short form - .IIF - can be used instead
Example:
.IIF NE DEBUG JSB PRINT_WHERE
If DEBUG <> 0, the following line will be assembled
JSB PRINT_WHERE
D. A repeat assembly block can be used to cause a block of code to be
assembled repeatedly (in effect throwing the assembler into a loop). It
consists of:
1. One of the following repeat directives
a. .REPEAT expression - where expression is an ASSEMBLY-TIME
expression that evaluates to a positive integer
Example:
.REPEAT 10
MOVO #-1, (R0)+
.ENDR
Is equivalent to
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
MOVO #-1, (R0)+
b. .IRP symbol list - the repeat block code is assembled once for
each item on the list, with the symbol replaced by the
appropriate element (in essence, an ASSEMBLY-TIME for loop)
Example:
.MACRO PUSH_LIST LIST
.IRP ITEM, LIST
PUSHL ITEM
.ENDR
.ENDM PUSH_LIST
If called by
PUSH_LIST <R0, R3, R5, R7>
This would expand to
PUSHL R0
PUSHL R3
PUSHL R5
PUSHL R7
c. .IRPC symbol string is similar, but the repeat block is assembled
once for each character in the string.
2. A .ENDR directive terminates the repeat block
E. As is true in programming languages, conditional and repeat blocks can
be nested within each other to any depth up to a maximum of 31 - e.g.
the following is legal
.IF GT FOO
.IF GT BAR
.REPEAT FOO+BAR
...
.ENDR
.ENDC
.ENDC
If both FOO and BAR are greater than zero, then the code inside the
repeat block is assembled FOO+BAR times. If either is <= zero, the
REPEAT block is totally ignored.
F. MACRO also supplies some additional directives that can be useful
with conditional and repeat assembly blocks that occur in macros.
1. .NARG symbol -- Sets symbol to the number of actual arguments to the
current macro call
Example:
.MACRO FOO A, B, C
.NARG N
...
.ENDM FOO
If called by
FOO R0, R1
The symbol N would have the value 2
2. .NCHR symbol, string -- sets symbol equal to the number of characters
in string.
3. .NTYPE symbol, operand -- sets symbol equal to the operand specifier
byte corresponding to operand
Example: A macro to access the i'th element of some array of
longwords and copy it to some destination. If I is a
register, we can use indexed mode addressing; otherwise,
we need to copy I to a register first
.MACRO ELT, ARRAY, I, DEST
.NTYPE ITYPE, I ; Sets ITYPE to operand
; specifier corresponding to I
.IF NE <ITYPE & ^XF0>-^X50 ; Mode is NOT 5
PUSHL R0
MOVL I, R0
MOVL ARR[R0], DEST
MOVL (SP)+, R0
.IFF
MOVL ARR[I], DEST
.ENDC
Copyright ©1999 - Russell C. Bjork