MACRO Made Easy – Part VII: More MACRO Macros
MACRO Made Easy
Part VII: More MACRO Macros
by
Hunter Goatley
Western Kentucky University
A note before beginning: this article talks a lot about the efficiencies of certain VAX instructions. Note that these conversations don’t necessarily apply to the MACRO-32 compilers on Alpha and Itanium. The methods described still work, but they may or may not be more efficient than what the compilers choose to do on those platforms.
Finishing with the Character Instructions
Last issue, we covered a number of the very powerful instructions for manipulating characters on the VAX. There are five more character instructions that are not used very often, so we’ll give them a cursory glance this time. The first one is the Calculate Cyclic Redundancy Check (CRC) instruction. It is used to provide checks during communications and to provide support for software that performs checksums. Since I’ve never had any reason to use the instruction, I’ll skip any further discussion; for more information, see the VMS manual VAX MACRO and Instruction Set Reference Manual or the VAX Architecture Handbook.
The other four instructions all use a 256-byte table of characters to perform their work. The instructions are:
- MOVTC — MOVe Translated Characters
- MOVTUC — MOVe Translated Until Character
- SPANC — SPAN Characters
- SCANC — SCAN Characters
As their names imply, MOVTC and MOVTUC are very useful for translating characters as they are being copied. Perhaps the most common usage is to convert lowercase characters to uppercase as they are copied.
The format of the instruction usage is:
MOVTC srclen,srcaddr,fill,tbladdr,dstlen,dstaddr
The operands are identical to those accepted by MOVC5, except that the fill byte is followed by the address of a 256-byte table. Each character being moved is used as an index into this table and the byte at that table index is copied to the destination buffer. The MOVTC and MOVTUC are not used very often, mostly because the building of the table can be cumbersome, unless you employ some assembler directives. For example, the code below, which only converts lowercase to uppercase, uses a work symbol (SEED) and the .REPEAT directive to store successive bytes representing each character in the ASCII character set.
SEED = 0 UPCASE: .REPEAT <^A/a/> ;*Repeat up to 97 times .BYTE SEED ;*Store the byte in the table SEED = SEED + 1 ;*Bump the seed .ENDR ;*Loop until done ; ; Now put the uppercase letters in instead of the lowercase letters. ; .ASCII /ABCDEFGHIJKLMNOPQRSTUVWXYZ/ SEED = <^A/z/+1> ;*Reset seed at "z" + 1 .REPEAT <256-SEED> ;*Fill out rest of table .BYTE SEED ;*... SEED = SEED + 1 ;*... .ENDR ;*... STRING: .ASCII /this is a lowercase string/ STRING_LEN = .-STRING OUTBUF: .ASCII / / .ENTRY UPCASE_IT,^M<> MOVTC #STRING_LEN,STRING,#0,UPCASE,#STRING_LEN,OUTBUF RET .END UPCASE_IT
The table could also have been built using successive .BYTE directives:
UPCASE: .BYTE 0,1,2,3,4,5,6,7,8,9,10,11,.... .BYTE 20,21,22,23,24,... ..... .BYTE ...,251,252,253,254,256
But you can see how tedious that would have been if all we wanted to do is convert lowercase to uppercase. It should also be obvious that MOVTC could be extremely effective if you were implementing some cipher algorithm, since each character in a string could be replaced within a single VAX instruction.
The difference between MOVTC and MOVTUC is that MOVTUC will stop either when the strings are exhausted or an escape character is reached in the source string. The escape character replaces the fill character in the MOVTC instruction:
MOVTUC srclen,srcaddr,esc,tbladdr,dstlen,dstaddr
If the escape character is reached, the V-bit condition code is set (which can be checked using a BVS instruction (Branch V-bit Set). MOVTUC is most useful when you want to translate everything up to a particular character. For example, suppose your program contained a buffer consisting of lines of terminal input, where each line is terminated by a carriage return (<CR>) in the buffer. You could copy (and/or translate) each line of input using the following instruction:
MOVTUC #INBUFF_LEN,INBUFF,#13,COPY,#STRING_LEN,DESTINATION BVS ESCAPE_FOUND ; Branch if escape found
The string INBUFF would be copied until either a <CR> was found or the whole string was copied. To suppress any translation on the string, the following sequence could be used to build the table:
SEED = 0 COPY: .REPEAT 256 .BYTE SEED SEED = SEED + 1 .ENDR
SCANC and SPANC also use a 256-byte table, but the usage is a little different. The format of these instructions is as follows:
SxANC len,addr,tbladdr,mask
Each byte in the string is again used as an index into the 256-byte table. The byte selected is then ANDed with the MASK operand. The operation continues until the result is zero for SPANC or non-zero for SCANC. The values left in R0 and R1 are the number of remaining bytes, including the “offending” character, and the address of that character. In it’s simplest case, you could use SCANC to locate any control characters (ASCII values 0–31) in a string:
TABLE: .REPEAT 32 .BYTE 1 .ENDR .REPEAT <256-32> .BYTE 0 .ENDR INBUFF: .ASCII /Hello, / .BYTE 13 .ASCII /Goodbye/ INBUFF_LEN = .-INBUFF .... SCANC #INBUFF_LEN,INBUFF,TABLE,#1 ; ; Here, R1 points to the 13 (<CR>) and R0 contains 8
As I said above, these character instructions are not used very often, but they can be extremely useful in the proper program.
Writing Character Macros
Now that we’ve seen most of the VAX character instructions, let’s turn our attention to writing some rather complex macros that can be helpful when working with the character instructions. You may recall that we’ve written a couple of short macros before, most notably the .ITMLST macros in “MACRO Made Easy, Part 5” (April 1992).
First, let’s look at the MOVS macro, which is pictured in Program 1. MOVS was designed to let you copy short strings to buffers without having to create storage space in the data program section (psect). MOVS will generate a series of MOVL, MOVW, and MOVB instructions with the string broken up into longword, word, and byte ASCII literals. The length of the string can also be copied to an optional DESTLEN parameter. For example, the following invocation:
MOVS <Table addresses: >,OUTBUFF,OUTBUFF_LEN
would produce the following instructions:
PUSHL R0 MOVAB OUTBUFF,R0 MOVL #^A/Tabl/,(R0)+ MOVL #^A/e ad/,(R0)+ MOVL #^A/dres/,(R0)+ MOVL #^A/ses:/,(R0)+ MOVB #^A/ /,(R0)+ POPL R0 MOVL #17,OUTBUFF_LEN
Such sequences can be useful when writing MACRO. The alternative would’ve have been to create the string in the data psect:
STRING: .ASCII /Table addresses: / STRING_LEN = .-STRING .... MOVC3 #STRING_LEN,STRING,OUTBUFF
Let’s start with the concepts used by the macro. The first thing it should do is determine the number of longwords, words, and bytes in the string so it knows how many, and which, MOVx instructions should be generated. This can be determined using the %LENGTH assembler string operator, which returns the length of a string. The following lines show the .MACRO statement and the initializing of some work symbols:
.MACRO MOVS STRING,DEST,DESTLEN ; ; Initialize the work symbols. ; ..MOVS_LENGTH = %LENGTH(STRING) ;Get the string length ..MOVS_POS = 0 ;Offset into string starts at 0
Using the example above (), the assembler symbol ..MOVS_LENGTH is equated with value 17. ..MOVS_POS is used as a pointer into the STRING when we actually produce the MOVx instructions.
Now that we’ve got the length, we need to calculate the number of longwords, words, and bytes that the string can be broken into. For efficiency, the string should be broken up into as many large pieces as possible. The following lines create the symbols ..MOVS_L_CNT, ..MOVS_W_CNT, ..MOVS_B_CNT:
; ; Count the number of longwords, words, and bytes in the string. ; ..MOVS_L_CNT = <..MOVS_LENGTH / 4> ..MOVS_W_CNT = <..MOVS_LENGTH / 2> & 1 ..MOVS_B_CNT = ..MOVS_LENGTH & 1
For our examples, the values are 4, 0, and 1, respectively. Next, the macro saves the contents of R0 on the stack and initializes R0 to point to the destination address:
PUSHL R0 ;Save R0 for work MOVAB DEST,R0 ;Destination address -> R0
Now we’re ready to generate the MOVx instructions. We already know how many of each should be generated, but how can the string be broken up into the desired ASCII literals? The method used in MOVS is the %EXTRACT string operator. It works just like the SUBSTR() in high-level languages, except that it produces no code—it is strictly an assembler operator, like the unary and binary operators (+, -, *, /, etc.). The %EXTRACT is embedded in the appropriate MOVx instruction like so:
MOVL #^A/%EXTRACT(..MOVS_POS,4,<STRING>)/,(R0)+
The instruction above would extract 4 bytes from STRING, starting at the offset equated with ..MOVS_POS (0, initially). The <> are needed around STRING to keep the assembler from complaining that there are too many operands. If ..MOVS_POS is 0, the resulting instruction would be:
MOVL #^A/Tabl/,(R0)+
Once again, the .REPEAT and .ENDR assembler directives are used to generate the appropriate number of MOVx instructions. Since we have the appropriate values defined as ..MOVS_x_CNT, we can use those as .REPEAT operands:
.REPEAT ..MOVS_L_CNT MOVL #^A/%EXTRACT(..MOVS_POS,4,<STRING>)/,(R0)+ ..MOVS_POS = ..MOVS_POS + 4 ;Point to next bytes .ENDR
In this example, the value of MOVS_L_CNT is 4, so the instructions between the .REPEAT and the .ENDR will be executed four times. Each time a MOVL is generated, the value of ..MOVS_POS is incremented by 4 to point to the next byte after the longword just used. The other three generated instructions would look like these:
MOVL #^A/e ad/,(R0)+ MOVL #^A/dres/,(R0)+ MOVL #^A/ses:/,(R0)+
Once the MOVLs have been processed, the same thing is done for the words and bytes:
.REPEAT ..MOVS_W_CNT MOVW #^A/%EXTRACT(..MOVS_POS,2,<STRING>)/,(R0)+ ..MOVS_POS = ..MOVS_POS + 2 ;Point to next bytes .ENDR ; ; Now do the byte, if there is one. ; .REPEAT ..MOVS_B_CNT MOVB #^A/%EXTRACT(..MOVS_POS,1,<STRING>)/,(R0)+ .ENDR
Note that there will be at most only one MOVW and only one MOVB instruction. However, the .REPEAT-.ENDR combination was used for consistency and flexibility, and because I believe I found a bug in the assembler. If a .IF conditional was used, the %EXTRACT failed to produce the proper character. Using .REPEAT generated the proper MOVB instruction.
Now we need to restore the contents of our work register (R0):
POPL R0 ;Restore contents of R0
Finally, we’re ready to copy the string length to the DESTLEN parameter, if it was specified on the macro call. This is accomplished using the .IIF directive, which is an immediate conditional. That is, it can be used to produce a single line of code.
; ; Leave length in DESTLEN, if specified. ; .IIF NOT_BLANK, DESTLEN, MOVL #..MOVS_LENGTH,DESTLEN
This line says that if DESTLEN is “not blank” (it has a value), then generate the MOVL instruction. Otherwise, move on to the next line, which is the .ENDM line:
.ENDM MOVS ;*End of MOVS macro
Make sense? You might want to study it a little more before moving on, because the next macro (macros) is considerably more involved. Also, the .IF conditional will be examined in more detail.
The MOVCX Macro
As I mentioned last issue, the MOVC3 instruction can be rather costly, depending on the VAX platform (since the instruction is emulated in software on many VAXes today). This is especially true when MOVC3 is used to copy short strings of no more than, say, 20 bytes. In those cases, a series of MOVQ, MOVL, MOVW, and MOVB instructions will be more efficient. Additionally, many programs use registers R0–R5 and must save and restore them before and after the MOVC3 instruction is executed. To gain maximum efficiency and minimal programmer headache, I wrote a macro called MOVCX that will automatically save and restore any work registers, and, more importantly, will determine whether the length is a literal and, if so, will generate the appropriate MOVx instructions for short strings.
Program 2 depicts the MOVCX macro and its supporting macros PUSHREG and POPREG.
The first three parameters to MOVCX are the same as for MOVC3: the length, the source address, and the destination address. It also takes two more parameters: a flag, PRESERVE, that determines whether or not the registers should be saved, and LIMIT, which determines the maximum size of a “short” string that will be copied with MOVx instructions. The defaults are PRESERVE=YES and LIMIT=32, as can be seen on the .MACRO line:
.MACRO MOVCX LENGTH,SRC,DEST,PRESERVE=YES,LIMIT=32
Before looking at the macro, let’s look again at the algorithm used. The first thing it does is determine whether or not the LENGTH parameters is a short literal or immediate value (preceded by #, S^#, or I^#). For example, the following are all literals:
MOVCX #3,SRC,DEST MOVCX #STRING_LEN,STRING,OUTBUFF MOVCX S^#STRING_LEN,STRING,OUTBUFF MOVCX I^#STRING_LEN,STRING,OUTBUFF ;Immediate
Immediate mode would not normally be used, but it is handled by the macro.
If the length is not a literal, a normal MOVC3 is generated, with R0–R5 preserved if desired. If the length is a literal, it is compared with the LIMIT (32, by default). If it exceeds the limit, once again, a normal MOVC3 is generated, since it is probably more efficient than multiple MOVx instructions.
Finally, if the length does ot exceed the limit, the proper number of MOVQ, MOVL, MOVW, and MOVB instructions are generated to copy quadwords, longwords, words, and bytes. Notice that there will be at most one longword, one word, and one byte to copy. For example, if the length is twenty bytes, it can be copied as two quadwords (16 bytes) and a longword (4 bytes). If the length were 22, it would be two quadwords, one longword, and one word, etc.
The MOVCX macro uses the assembler directives .NTYPE, .IF, and .REPEAT. .NTYPE returns the addressing mode of the given symbol; it is used to determine whether or not the given length is a literal. .IF lets us implement conditional assembly, and we’ve already seen .REPEAT used in MOVS.
Before looking at MOVCX itself, let’s look at its two supporting macros, PUSHREG and POPREG. MOVCX will need to save and restore registers in several places. Because the PUSHR and POPR instructions are rather costly, I wanted to use PUSHLs and POPLs, but I didn’t want them cluttering up the MOVCX macro. The solution I chose was create PUSHREG and POPREG macros and nest calls to them inside MOVCX.
In the .ITMLST macros presented in Part 5, I used the .IRP (Indefinite RePeat) assembler directive. I also used it in PUSHREG and POPREG to process each of the registers that are to be saved and restored. The call to PUSHREG and POPREG should include a comma-separated list of registers, surrounded by angle brackets:
PUSHREG <R0,R1,R2,R3,R4,R5>
The actual macro definition is as follows:
.MACRO PUSHREG REGS ;*Push registers on stack; .IRP REG,<REGS> ;*For each register given; PUSHL REG ; Save contents of REG; .ENDR ;*Loop until no more; .ENDM PUSHREG ;*End of PUSHREG macro;
The .IRP REG,<REGS> directive processes each register in the comma-separated list; the generated code consists of PUSHL R0, PUSHL R1, etc. Because POPREG is implemented the same way, it is important to make sure that the registers are specified in exactly the opposite order as their order for PUSHREG to maintain the LIFO stack order (last in, first out):
POPREG <R5,R4,R3,R2,R1>
This is not very flexible from a programmer’s view, but since these macros were written primarily to support MOVCX, I decided that it was OK.
Now let’s take a look at the MOVCX macro. The .MACRO line and a symbol initialization come first:
.MACRO MOVCX LENGTH,SRC,DEST,PRESERVE=YES,LIMIT=32 ..MOVCX_X = 0 ;Initialize the work variable
..MOVCX_X will be used later in the macro to hold an offset into a string. It should be initialized here so that it’s use later won’t result in the assembler complaining that the symbol is undefined or not absolute.
The first thing MOVCX should do is check to see if the specified length is a literal. As mentioned above, this is accomplished using the .NTYPE directive, which returns the addressing mode of the specified operand. The addressing mode is normally an 8-bit value, though it can be 16 bits. Bits 0–3 represent the register used in the addressing mode; bits 4–7 represent the mode itself. Normally, literals are determined by bits 4–7 having a value in the range 0–3. However, a literal causes .NTYPE to return with bits 4–7 as all zeros. Immediate mode is indicated by the value 1. The following line shows the .NTYPE call:
.NTYPE ..MOVCX_LEN_TYPE,LENGTH ;Get the addressing mode
The addressing mode of LENGTH is equated with the symbol ..MOVCX_LEN_TYPE. Since we’re only interested in bits 4–7, out of a possible 16, we need to mask out the rest. This is accomplished by shifting the value right 4 bits and ANDing the result with ^XF to mask out the upper nibble(s):
..MOVCX_LEN_TYPE = <..MOVCX_LEN_TYPE@-4> & ^XF
The ‘@’ is the shift operator; “-4” says, “Shift right 4 bits.”
We can now check to see if the type is a literal or an immediate (0 or 1). This is done using the .IF assembler directive. The general format of the .IF directive is:
.IF condition parameter(s) ... ... statements ... .ENDC
The condition is one of the following:
Condition | Abbrev. | Complement | Abbrev. | # of params |
---|---|---|---|---|
|
||||
EQUAL | EQ | NOT_EQUAL | NE | 1 expression |
GREATER | GT | LESS_EQUAL | LE | 1 expression |
LESS_THAN | LT | GREATER_EQUAL | GE | 1 expression |
DEFINED | DF | NOT_DEFINED | NDF | 1 symbol |
BLANK | B | NOT_BLANK | NB | 1 (only in macros) |
IDENTICAL | IDN | DIFFERENT | DIF | 2 (only in macros) |
When an expression is given, it is evaluated and compared with 0. If the evaluation results in a true statement, the lines following the .IF are expanded. For example, if symbol TRUE has a value of 1, the following lines would produce CLRL R0:
.IF EQ TRUE CLRL R0 .ENDC
There are also directives for handling other cases: .IF_FALSE, .IF_TRUE, .IF_TRUE_FALSE. These are most frequently used in cases like the following:
.IF condition parameter(s) ... ... statements if true ... .IF_FALSE ... ... statements if false ... .ENDC
The DEFINED and NOT_DEFINED conditionals let you write VMS version-specific code and easily assemble debug instructions or leave them out. They take one operand: a symbol. If the symbol is defined (or not defined), the lines between are included. For example, if you have code that depends on a certain value being defined, you could use the .IF conditionals to avoid undefined symbol errors when compiling on a machine running without that value. The following code would assemble properly on all systems, even on a version of VMS before VT400-series terminals were defined:
.IF DEFINED TT$_VT400_Series ;If the VT400 is known, MOVL #TT$_VT400_Series,R0 ;... set it to that .IF_FALSE ;Otherwise, set it to MOVL #TT$_VT100,R0 ;... VT100 .ENDC ;*End of conditional
As you can see, this is functionally equivalent to #ifdef and #ifndef in the C programming language.
The BLANK, NOT_BLANK, IDENTICAL, and DIFFERENT conditionals can only be used with macros. BLANK and NOT_BLANK can be used to see if the named macro parameter was given or not. We used NOT_BLANK with the .IIF in MOVS:
.IIF NOT_BLANK, DESTLEN, MOVL #..MOVS_LENGTH,DESTLEN
The .IIF works the same way as .IF, except that it can only produce a single instruction. The line above could also have been written as:
.IF NOT_BLANK DESTLEN MOVL #..MOVS_LENGTH,DESTLEN .ENDC
Finally, DIFFERENT and IDENTICAL can be used to check the value that was passed as a macro parameter. We’ll see an example of that momentarily.
Getting back to the MOVCX macro, we can check to see if the operand type is literal or immediate with the following conditional:
.IF LE <..MOVCX_LEN_TYPE-1>
The expression, <..MOVCX_LEN_TYPE-1>, is evaluated and compared with 0. If MOVCX_LEN_TYPE is either 0 or 1, subtracting 1 from it will leave 0 or -1, which is LE 0. So the statements following the .IF up to either .IF_FALSE or .ENDC will be expanded.
Assuming the value is a literal, we need to see if it falls within the specified limit for character strings. The parameter LENGTH is equated to the original literal string, which includes the ‘#’ (or “S^#” or “I^#”). We need to remove the ‘#’ before we can use LENGTH as a number for the limit test. This is accomplished with the following lines:
.IF IDN <#>,<%EXTRACT(0,1,LENGTH)> ..MOVCX_X = %LENGTH(LENGTH) ..MOVCX_LENGTH = <%EXTRACT(1,..MOVCX_X,LENGTH)> .ENDC
The first line extracts the first character of LENGTH and compares that with the string <#>. If it’s equal, it gets the length of the LENGTH string, then equates a new symbol, ..MOVCX_LENGTH, with the remaining number using the %EXTRACT string operator. For example, assume MOVCX was invoked with the following line:
MOVCX #10,STRING,BUFFER
After evaluation, ..MOVCX_LENGTH would be equated to 10. If a symbol is passed in:
MOVCX #STRING_LEN,STRING,BUFFER
..MOVCX_LENGTH would receive the value of the symbol STRING_LEN. Additional checks are made to handle <S^#> and <I^#>.
; ; If "S^" is present (short literal), remove it. ; .IF IDN <S^#>,<%EXTRACT(0,3,LENGTH)> ..MOVCX_X = %LENGTH(LENGTH) ..MOVCX_LENGTH = <%EXTRACT(3,..MOVCX_X,LENGTH)> .ENDC ; ; If "I^" is present (immediate mode), remove it. ; .IF IDN <I^#>,<%EXTRACT(0,3,LENGTH)> ..MOVCX_X = %LENGTH(LENGTH) ..MOVCX_LENGTH = <%EXTRACT(3,..MOVCX_X,LENGTH)> .ENDC
Now that the string length is equated with ..MOVCX_LENGTH, we can compare that with the LIMIT parameter. Notice that LIMIT has a default value, we don’t need to check to see if it’s there; however, we could have with a line like the following:
.IF NOT_BLANK LIMIT
The limit check is performed with the following conditional:
.IF GT <..MOVCX_LENGTH - LIMIT>
At this point, if the length is greater than the specified limit, a normal MOVC3 is generated. The registers are saved and restored if the PRESERVE parameter value is identical to YES:
.IIF IDENTICAL PRESERVE,<YES>, - PUSHREG <R5,R4,R3,R2,R1,R0> MOVC3 LENGTH,SRC,DEST .IIF IDENTICAL PRESERVE,<YES>, - POPREG <R0,R1,R2,R3,R4,R5>
If the value didn’t exceed the limit, the following code is expanded:
.IF_FALSE ;Didn't exceed LIMIT ; ; Now count the number of quadwords, longwords, words, ; and bytes in the string. ; ..MOVCX_Q_LEN = <..MOVCX_LENGTH / 8> ..MOVCX_L_LEN = <..MOVCX_LENGTH / 4> & 1 ..MOVCX_W_LEN = <..MOVCX_LENGTH / 2> & 1 ..MOVCX_B_LEN = <..MOVCX_LENGTH> & 1
As in MOVS, the lines above calculate the number of quadwords in the string, followed by whether or not there is a longword, word, or byte left over. Next the registers are saved (if desired) and the SRC and DEST addresses are copied to to R0 and R1:
; ; Save registers, if necessary. ; .IIF IDENTICAL PRESERVE,<YES>, PUSHREG <R0,R1> MOVAB SRC,R0 ;Move SRC address to R0 MOVAB DEST,R1 ;Move DEST address to R1
Now we’re ready to generate the actual MOVQ, MOVL, MOVW, and MOVB instructions. Again, this code is almost identical to that generated by MOVS, except the string is not an ASCII literal string:
.IF NE ..MOVCX_Q_LEN ;*If there are any of this type .REPEAT ..MOVCX_Q_LEN ;... generate that number of MOVQ (R0)+,(R1)+ ;... MOVQ instructions .ENDR ;*Loop until no more MOVQs .ENDC ;*End of .IF .IIF NE, ..MOVCX_L_CNT, MOVL (R0)+,(R1)+ .IIF NE, ..MOVCX_W_CNT, MOVW (R0)+,(R1)+ .IIF NE, ..MOVCX_B_CNT, MOVB (R0)+,(R1)+
Finally, if the operand type was not a literal, a normal MOVC3 is generated. This is the .IF_FALSE part of the original .IF that checked to see if the operand type was a literal or immediate:
.IF_FALSE .IIF IDENTICAL PRESERVE,<YES>, PUSHREG <R5,R4,R3,R2,R1,R0> MOVC3 LENGTH,SRC,DEST ;Just use a MOVC3 .IIF IDENTICAL PRESERVE,<YES>, POPREG <R0,R1,R2,R3,R4,R5> .ENDC .ENDM MOVCX ;*End of MOVCX macro
Some Samples
The following lines show some invocations of the MOVS and MOVCX macros:
MOVCX STR_D,@STR_D+4,OUTBUF ;Generates a MOVC3 MOVAB OUTBUF,(R1) MOVCX S^#STR_LEN,STR,(R1) MOVCX I^#STR1_LEN,STR1,(R1) MOVCX #STR1_LEN,STR1,(R1),PRESERVE=NO MOVS <This is a test! @>,(R1) MOVAB OUTBUF,R4 MOVS <SYSTEM>,JIB$T_USERNAME(R4),R0 ;Leave length in R0
As you can see, you can write some pretty complex macros. Even if you don’t find a use for MOVS and MOVCX, you should be ready to try to write your own macros to generate the code you want.
NEXT TIME….
Next issue, we’ll look at some macros provided with VMS and also look at doing file I/O from MACRO using RMS.
Hunter Goatley, goathunter@WKUVX1.BITNET, Western Kentucky University, Bowling Green, KY.
Program 1
;+ ; ; Macro: MOVS ; ; Author: Hunter Goatley, goathunter@WKUVX1.BITNET ; May 25, 1992 ; ; Functional description: ; ; The MOVS macro copies a given string to a given destination ; address. The length of the string can be optionally copied ; to the DESTLEN parameter. ; ; This macro is useful when you want to copy small ASCII strings ; to a buffer, without creating the string in a data area. For ; example: ; ; MOVS <SYSTEM>,JIB$T_USERNAME+1(R3),R0 ; MOVB R0,JIB$T_USERNAME(R3) ; ; The sequence above would generate the following instructions: ; ; PUSHL R0 ; MOVAB JIB$T_USERNAME+1(R3),R0 ; MOVL #^A/SYST/,(R0)+ ; MOVW #^A/EM/,(R0)+ ; POPL R0 ; MOVL #6,R0 ; MOVB R0,JIB$T_USERNAME(R3) ; ;- .MACRO MOVS STRING,DEST,DESTLEN ; ; Initialize the work symbols. ; ..MOVS_LENGTH = %LENGTH(STRING) ;Get the string length ..MOVS_POS = 0 ;Offset into string starts at 0 ; ; Count the number of longwords, words, and bytes in the string. ; ..MOVS_L_CNT = <..MOVS_LENGTH / 4> ..MOVS_W_CNT = <..MOVS_LENGTH / 2> & 1 ..MOVS_B_CNT = <..MOVS_LENGTH> & 1 PUSHL R0 ;Save R0 for work MOVAB DEST,R0 ;Destination address -> R0 ; ; For each longword that can be moved, generate an instruction ; like the following: ; MOVL #^A/TEXT/,(R0)+ ; .REPEAT ..MOVS_L_CNT MOVL #^A/%EXTRACT(..MOVS_POS,4,<STRING>)/,(R0)+ ..MOVS_POS = ..MOVS_POS + 4 ;Point to next bytes .ENDR ; ; Now do any words that can be done. ; .REPEAT ..MOVS_W_CNT MOVW #^A/%EXTRACT(..MOVS_POS,2,<STRING>)/,(R0)+ ..MOVS_POS = ..MOVS_POS + 2 ;Point to next bytes .ENDR ; ; Now do the byte, if there is one. ; .REPEAT ..MOVS_B_CNT MOVB #^A/%EXTRACT(..MOVS_POS,1,<STRING>)/,(R0)+ .ENDR POPL R0 ;Restore contents of R0 ; ; Leave length in DESTLEN, if specified. ; .IIF NOT_BLANK, DESTLEN, MOVL #..MOVS_LENGTH,DESTLEN .ENDM MOVS ;*End of MOVS macro
Program 2
;+ ; ; Macro: PUSHREG & POPREG ; ; Author: Hunter Goatley, goathunter@WKUVX1.BITNET ; May 25, 1992 ; ; Functional description: ; ; PUSHREG and POPREG accept as inputs a list of registers whose ; contents are pushed onto and popped off of the stack using ; PUSHL and POPL instructions. This is faster than using ; the PUSHR and POPR instructions. ; ; WARNING: the list of registers passed to POPREG must be in the exact ; opposite order from the list passed to PUSHREG. For example: ; ; PUSHREG <R0,R1,R2> ; POPREG <R2,R1,R0> ; ;- .MACRO PUSHREG REGS ;*Push registers on stack; .IRP REG,<REGS> ;*For each register given; PUSHL REG ; Save contents of REG; .ENDR ;*Loop until no more; .ENDM PUSHREG ;*End of PUSHREG macro; .MACRO POPREG REGS ;*Pop registers off stack; .IRP REG,<REGS> ;*For each register given; POPL REG ; Restore contents of REG; .ENDR ;*Loop until no more; .ENDM POPREG ;*End of POPREG macro; ;+ ; ; Macro: MOVCX ; ; Author: Hunter Goatley, goathunter@WKUVX1.BITNET ; May 25, 1992 ; ; Functional description: ; ; MOVCX is designed to serve as a functional replacement for MOVC3. ; Because MOVC3 is a relatively expensive instruction on most VAX ; processors, it is desirable to use a combination of MOVQ, MOVL, ; MOVW, and MOVB instructions when copying small strings. MOVCX ; will automatically determine if the given length parameter is ; a literal value (known at assembly-time) and, if so, will generate ; the appropriate MOVx instructions to copy the string. If the ; string length is not a literal, or is greater than 32 bytes, a ; MOVC3 is generated. In both cases, the macro will, by default, ; preserve the registers used as scratch registers. ; ; NOTE: this macro also calls the PUSHREG, POPREG, and MOVCX_GEN_MOVX ; macros. It also defines a number of symbols, all prefixed by the ; string "..MOVCX_". ; ; Formal parameters: ; ; LENGTH The length of the string ; SRC The address of the first byte of the source ; DEST The address of the first byte of the destination ; PRESERVE If YES (the default), all registers are preserved ; LIMIT MOVC3 size limit (default 32) ; ; If PRESERVE=NO and the length is a literal, R0 and R1 are used as ; work registers. ; ; Calling sequence: ; ; MOVCX #STRING_LEN,SOURCE,DESTINATION ; MOVCX (R3),(R4),(R5) ; MOVCX #STRING_LEN,SOURCE,DESTINATION,PRESERVE=NO ;Don't save regs ; ;- .MACRO MOVCX LENGTH,SRC,DEST,PRESERVE=YES,LIMIT=32 ..MOVCX_X = 0 ;Initialize the work variable ; Get the type of the length parameter. The type returned is ; either an 8-bit or 16-bit value. Bits 0--3 represent the ; register used; bits 4--7 represent the addressing mode. The ; addressing mode returned by .NTYPE isn't exactly the same as ; the normal addressing mode encoding. Specifically, a literal ; is specified by bits 4--7 being all zero (instead of holding ; one of the values 0--3). ; .NTYPE ..MOVCX_LEN_TYPE,LENGTH ;Get the addressing mode ; Since we're only interested in bits 4--7, shift the type ; returned right 4 bits, then AND it with hexadecimal F to ; clear out the high byte in case a 16-bit value was returned. ; ..MOVCX_LEN_TYPE = <..MOVCX_LEN_TYPE@-4>&^XF ; Now check to see if the addressing mode is short literal ; (value is 0) or immediate (value is 1). .IF LE <..MOVCX_LEN_TYPE-1> ; ; Because it's a literal, the "#" should be present. ; Let's be safe, though, and check for it before using ; the length as a number (in symbol ..MOVCX_LENGTH). ; .IF IDN <#>,<%EXTRACT(0,1,LENGTH)> ..MOVCX_X = %LENGTH(LENGTH) ..MOVCX_LENGTH = <%EXTRACT(1,..MOVCX_X,LENGTH)> .ENDC ; ; If "S^" is present (short literal), remove it. ; .IF IDN <S^#>,<%EXTRACT(0,3,LENGTH)> ..MOVCX_X = %LENGTH(LENGTH) ..MOVCX_LENGTH = <%EXTRACT(3,..MOVCX_X,LENGTH)> .ENDC ; ; If "I^" is present (immediate mode), remove it. ; .IF IDN <I^#>,<%EXTRACT(0,3,LENGTH)> ..MOVCX_X = %LENGTH(LENGTH) ..MOVCX_LENGTH = <%EXTRACT(3,..MOVCX_X,LENGTH)> .ENDC ; ; At this point, ..MOVCX_LENGTH holds the length of the ; string to copy. ; ; If we exceed the length limit (32, by default), just ; generate a MOVC3 instruction (and save regs if desired). ; .IF GT <..MOVCX_LENGTH - LIMIT> .IIF IDENTICAL PRESERVE,<YES>, - PUSHREG <R5,R4,R3,R2,R1,R0> MOVC3 LENGTH,SRC,DEST .IIF IDENTICAL PRESERVE,<YES>, - POPREG <R0,R1,R2,R3,R4,R5> .IF_FALSE ;Didn't exceed LIMIT ; ; Now count the number of quadwords, longwords, words, ; and bytes in the string. ; ..MOVCX_Q_LEN = <..MOVCX_LENGTH / 8> ..MOVCX_L_CNT = <..MOVCX_LENGTH / 4> & 1 ..MOVCX_W_CNT = <..MOVCX_LENGTH / 2> & 1 ..MOVCX_B_CNT = <..MOVCX_LENGTH> & 1 ; ; Save registers, if necessary. ; .IIF IDENTICAL PRESERVE,<YES>, PUSHREG <R0,R1> MOVAB SRC,R0 ;Move SRC address to R0 MOVAB DEST,R1 ;Move DEST address to R1 .IF NE ..MOVCX_Q_LEN ;*If there are any of this type .REPEAT ..MOVCX_Q_LEN ;... generate that number of MOVQ (R0)+,(R1)+ ;... MOVQ instructions .ENDR ;*Loop until no more MOVQs .ENDC ;*End of .IF .IIF NE, ..MOVCX_L_CNT, MOVL (R0)+,(R1)+ .IIF NE, ..MOVCX_W_CNT, MOVW (R0)+,(R1)+ .IIF NE, ..MOVCX_B_CNT, MOVB (R0)+,(R1)+ ; ; Restore the registers, if they were saved. ; .IIF IDENTICAL PRESERVE,<YES>, POPREG <R1,R0> .ENDC ; ; Here, the length was not a literal, so just generate a MOVC3 ; instruction, preserving registers if necessary. ; .IF_FALSE .IIF IDENTICAL PRESERVE,<YES>, PUSHREG <R5,R4,R3,R2,R1,R0> MOVC3 LENGTH,SRC,DEST ;Just use a MOVC3 .IIF IDENTICAL PRESERVE,<YES>, POPREG <R0,R1,R2,R3,R4,R5> .ENDC .ENDM MOVCX ;*End of MOVCX macro