System 370/390 Assembler Coding Techniques By Mark Hanna Mark Hanna of Hanna & Associates Inc. in Edmond Oklahoma specializes in VSE installation, support and education. He has been working with and installing DOS since 1969. This article is a collection of coding techniques and suggestions that can be used to make Assembler programs more efficient. Recommendations on the use of symbols are also provided. The first section consists of suggestions to make the program easier to read and understand; the second section consists of coding techniques that allow the programmer to utilize the hardware better; and the third section describes some conventions in the use of symbols for more efficient utilization of assembler facilities. In the coding examples, an R is placed in front of the register. This is a must coding technique so the register usage will appear on the cross-reference listing. Documentation 1) Documentation should be done internally to make it easier on the reader to understand the function of the program and the sources the program uses. At the beginning of the program, consider coding the following: o program name; o caller (if any); o subroutines called; o user macros coded (I also like to list the IBM macros as it helps in performing maintenance); o a brief description of the program function; o a list of input and output files; and o register usage. 2) Within the program, code information on each line to help the reader understand what is going on. I code documentation for me more than the next reader because I may not remember in a few months why I did a certain thing. The documentation is treated as a note to myself. Do not use an obvious comment such as "place 16 in register 4" for the instruction LA R4,16. Instead, provide information such as "place loop count in reg 4" as the comment. 3) To improve readability, use spaces or place a line of asterisks following each unconditional branch or transfer of control. This will flag the point as the end of this piece of logic. 4) In front of major routines explain what is happening. Code a comment box enclosed in asterisks or dashes to contain the explanation. The how of what is occurring should be on each line of code and not in this explanation because the how will change more than the what. 5) Use the TITLE statement to document the major parts of a program and make the parts easier to locate while reading the listing. Examples of use would be in front of mainline routines, subroutines, work areas, constants, file definitions and file I/O areas. Coding Techniques Clearing To clear a register, subtract it from itself (SR instruction). SR Rx,Rx To clear an area of storage of up to 256 bytes to X`00', exclusive-OR it with itself (XC instruction). XC AREA,AREA To clear an area of storage that may be greater than 256 bytes, use move character long (MVCL). MVI AREA,X'00' FILL WITH LOW VALUES LA R14,AREA FROM ADDRESS LA R0,AREA+1 TO ADDRESS L R1,=F'3447' LENGTH OF MOVE (AREA TO CLEAR-1) LR R15,R1 R15 ALSO MVCL R14,R0 To clear a single type of storage, or initialize it to any value, use Move Immediate (MVI instruction). For example: to store a zero in a byte of storage. MVI AREA,C`0' Another technique to set up an area of storage of up to 256 bytes would be to place the character to be propagated in front of the field and code the example in Figure 1. Testing To test a register (for zero, plus or minus), set the condition code by loading it into itself (LTR instruction). Many load instructions do not set the conditions. LTR Rx,Rx To test an area of storage (for zero or non-zero), set the condition code by OR-ing it to itself (OC instruction). To test a single byte (for zeros, ones or mixed), set the condition code by using the Test under Mask (TM instruction) with a mask of all bits (X`F'). TM AREA,X`FF Swapping To swap two registers or areas of storage without using a third, use three exclusive-ORs. XR R1,R2 XC A(length),B XR R2,R1, or XC B(length),A XR R1,R2 XC A(length),B To swap the two halves of a byte, PACK (or UNPK) it. PACK A,A Gates/Setting Bits On and Off To change an unconditional branch (at location BRANCH) to a NOP, use NI BRANCH+1,X`0F'. (If MVI was used, it would replace the X2 field of the branch as well.) BRANCH B LABEL ... ... NI BRANCH+1,X`0F' To change a NOP (at location BRANCH) to a branch, use OI BRANCH+1,X`F0'. BRANCH BC 0,LABEL ... ... OI BRANCH+1,X`F0' Note that a gate can also be considered a switch, since a TM can be used to test the condition-code mask field of the gate. Thus, there is no need to define a separate switch simply because more than one branch point depends on the switch setting. Change a bit to one if it was zero and to zero if it was one -- use the Exclusive Or (XI) instruction. This is called complementing a bit. Other bit positions will be unchanged. To change a NOP (at location BRANCH) to a branch: BRANCH BC 0,LABEL ... ... XI BRANCH+1,X`F0' Rotation and Shifting To rotate the contents of register, use a double shift followed by an OR. For example, to rotate register three left N places, use SR R2,R2 Clear register 2 SLDL R2,N Shift 2 and 3 left N OR R3,R2 OR it back To rotate a small list of consecutive words, use the multiple load and store instructions (if enough registers are available). For example: LM R9,R5,WORDLIST LR R6,R0 STM R1,R6,WORDLIST This example uses an extra register, but saves two bytes by using LR instead of an L or ST instruction. Note that the MVC instruction may be used to shift an area of storage left only because of the sequence in which the bytes are moved. Indexing To load an index with a positive constant value not exceeding 4095, use LA Rx,const where const is the constant value. For example, to load the value of 16 into register one, code LA R1,16. To create an incremental index by a positive constant value not exceeding 4095, use LA Rx,const(Rx). For example, to increment register six by one code LA R6,1(R6). To place the sum of two index registers in a third, use LA R3,0(R1,R2). The displacement can be used to add a small positive constant, not exceeding 4095, at the same time. Be cautious when using the Load Halfword (LH) instruction to load an index. The high-order bit is taken as the sign and propagated through the left half of the register. Use an AND instruction such as N R12,=X`0000FFFF' after the LH to avoid problems. To avoid having to adjust a table length before using it in a BXLE or BXH, adjust the base address (symbolically) instead. See Figure 2. In this example, INCR is assumed to be the (symbolic) increment, and LENGTH is assumed to contain the table length in bytes. Caution: TABLE-INCR must stay within the control section, otherwise it may be below the base register value for the CSECT, making it unaddressable. An alternate method, which avoids the addressing problem mentioned previously, is to consider the length to be the index to the last entry, (i.e., INCR less than the number of bytes occupied). Thus an empty table would have a negative length and a one-entry table would have zero length. To avoid using a separate register for a BXLE or BXH, the loop must be designed so that the index reaches (or passes) the increment after the last iteration. The actual coding may take many forms, but there are basically only two cases: 1) Natural Sequence (positive increment) increment = ITEMSIZE initial index value = 2 * ITEMSIZE-LENGTH base address = first type of next-to-last entry looping instruction = BXLE (the last useful index value is 2 * ITEMSIZE) 2) Reverse Sequence (negative increment) increment = -ITEMSIZE base address = first byte of first entry looping instruction = BXH (the last useful index value is zero) Length is assumed to be the number of bytes occupied, first entry means the entry in the lowest storage location. Examples: (To simplify the coding, all values are assumed to be symbolically assigned and known at assembly time.) See Figure 3. Note that there is really no difference between a base register and an index register. If done carefully, base register modification can give the effect of indexing for SS instructions, or can provide double indexing for RX instructions (i.e., for processing two-dimensional arrays) Binary Arithmetic If you are concerned with only the low-order 24 bits, any of the techniques discussed under indexing may be used. If you are concerned with only the low-order 12 bits or less, Load Address may be used to subtract small positive constants (not exceeding 4095). For instance, to subtract X from the low-order 12 bits of register R1, use LA R1,4096-X(R1). Note that there will be a carry into the 13th bit (bit 19), so that is it important that no more than 12 low-order bits of the result be used. To subtract one from a register, user BCTR Rx,0. This is useful to adjust a length value before executing (with EX) a logical SS instruction. Decrementing a loop register is also useful. Division or multiplication by a power of two may be accomplished by shifting right of left. Character Manipulation Be cautious when using LH to load a two-byte character field in a register (see indexing). Be cautious when using Insert Character (IC) without first clearing the register. IC leaves bits 0-23 of the register unchanged. Load Address can be used to load a one-byte character constant into a register (e.g., LA R1,C`%'). The "immediate" instructions MVI, CLI, etc. can frequently be used to great advantage when doing character manipulation. To fill an area of storage with a particular character, use the following: MVI AREA,C` ' MVC AREA=1(L'AREA-1),AREA (See Clearing for filling an area with low-values X'00'.) To make sure a character string is in uppercase, use the OC instruction with a field of blanks. This will turn on the 4 bit of the 8421 bits of a byte. For example: OC REPLY,BLANKS BLANKS DC CL3` ' REPLY DC X`8191A1' LETTERS AJS in lower case To change a character field (letters A-Z) to lowercase, use the NC instruction with a field of blanks. This will turn off the 4 bit of the 8421 bits of a byte. For example: NC AREA,BLANKS BLANKS DC CL6` ' AREA DC C`AJS' To convert a field from hexadecimal to EBCDIC, the Translate instruction may be used. TR AREA(16),HRZHEX-240 HRZHEX DC C'0123456789ABCDEF' Miscellaneous To accomplish multiple-precision fixed point arithmetic, use AL and SL on all but the most significant word. These provide exactly the same results as A and S, but avoid the possibility of an overflow interrupt. Note that BAL and BALR insert the current condition-code in the high-order end of the specified register. This makes it possible to save the condition code or pass it to a subroutine. However, it also means that the programmer should be careful in doing any arithmetic with that register. To reduce the amount of wasted storage, order constants and work areas as follows: o LTORG statement; o all byte strings; o all halfwords (or multiple halfwords); o all fullwords (or multiple fullwords); and o all doublewords (or multiple doublewords). In a control section that contains no instructions, it is more common (and just as good) to reverse the sequence from all byte strings tothe end. Symbolic Definitions Switches Logical switches should be symbolic so the symbols (or combinations of them) may be used as mask fields in TM, NI, OI and XI instructions, and in address constants to define the initial settings. For example, suppose we define switches A, B and C as follows: SWA EQU 4 SWB EQU 8 SWC EQU 16 These are to represent bit positions in a byte called SWITCHES. We can then test if switches A and B are both on by using: TM SWITCHES,SWA+SWB Are both A and B on ? We can invert switches B and C by using: XI SWITCHES,SWA+SWC Furthermore, SWITCHES with A and C initially on can be defined by using: TM DC AL1(SWA+SWC) In practice, of course, the symbols should be more meaningful than SWA, etc. Such as EOF for end-of-file, NOTFND for a not- found condition. Dummy Control Sections The DSECT instruction is very useful. The purpose of DSECT is to allow symbols to describe an area defined in some other program to avoid explicit addressing in subroutines. For example, if a subroutine is entered with register one pointing to a parameter list, that parameter list may be symbolically defined in the subroutine as something like: PARMLIST DSECT LENGTH DS F ITEMSIZE DS H The parameters can then be obtained with meaningful instruction such as: USING R1,PARMLIST LH R5,ITEMSIZE instead of cryptic statements like: LH R5,4(R1) Once the use of the register to define an area has completed, use the DROP instruction to signal this to the assembler and the reader of the program. /* Was this article of value to you? If so, please let us know by circling Reader Service No. 00. FIGURE 1: Setting Up Storage MVC AREA,AREA-1 DC C` ' This field must proceed the AREA label AREA DS CL132 FIGURE 2: Adjusting Base Register LA R4,INCR Set increment register LR R2,R4 Initialize index to increment L R5,LENGTH Set register to length LOOP .......... TABLE-INCR (2) Process nth entry BXLE R2,R4,LOOP Loop until index reaches length FIGURE 3: Code for Indexing LA R3,ITEMSIZE Set increment register L R2,=A(2*ITEMSIZE-L'TABLE) Initialize register LOOP ... .. TABLE+L'TABLE-2*ITEMSIZE (2) Process nth entry BXLE R2,R3,LOOP Count iterations LH R3,=Y(0-ITEMSIZE) Set increment L R2,=A(L'TABLE-ITEMSIZE) Initialize index LOOP2... ... TABLE (2) Process nth entry BXH R2,R3,LOOP2 Count iterations