Home   Assembler  Javascript    

Hints and tips

 

These hints and tips came out of a knowledge and techniques sharing session at UK software vendor in the late eighties, but they are still probably as useful and valid today. So many thanks to those whose contributions made it to this page.

They are not a tutorial as such, but are intended as useful code techniques that can be used in new and maintenance development, and also understanding assembler that may be encountered when maintaining legacy code.

The hints and tips on this page are grouped into three areas:

Caveat: The code samples below are intended as a starting point for your own use, it is your responsibility to ensure they works correctly in your situation.
For instance, the code below assumes that 0 through 15 have been equated to R0 through R15, whereas sometimes equates are set-up so that 0 through 15 become R0 through R9, RA through RF.

Processing 3270 screen input

There are still a lot of CICS and IMS/DC transactions out there that use Assembler, so the first group of hints and tips are all aimed at processing and validating 3270 screen input.

Input area search

Suppose you want to search an input area of some kind (especially one which you expect to be mostly blank), and process each non-blank character in it. Of course  there is the quick and dirty solution:

 

         LA  R14,AREA       R14 --> Start of input area
         LA  R15,L’AREA     R15 = Length of input area
LOOP     CLI 0(R14),C’ ’    Is this byte blank ?
         BE  NEXT           Yes, Ignore it
*                           No,  take appropriate action
*                                R14 points to a non-blank character
NEXT     LA  R14,1(,R14)    R14 --> Next input byte
         BCT R15,LOOP       Repeat until end of input area ?

 

The more performance minded might use TRT (translate and test) , but this has certain disadvantages: it needs a 256 byte table with a non-zero value in each byte except the one at offset X’40’, it uses R1 and R2 implicitly which might be inconvenient in the context of a real program and it’s awkward if the input area is more than 256 bytes long because you have to repeat it for each 256-byte chunk.

 

 

The following alternative method of the TRT method over the CLI loop method, but without the disadvantages.

 

LA   R14,AREA            R14 --> Start of input area
         LA   R15,L’AREA          R15 = Length of input area
         L    R5,=AL1(C' ',0,0,0) R4 = Blank pad char + zero length
LOOP     CLCL R14,R4              Search for a non-blank character
         BE   ALLBLANK            Exit if none found
*      (take appropriate action - R14 points to a non-blank character)
NEXT     LA   R14,1(,R14)         Don’t find same non blank again
         BCT  R15,LOOP            Continue unless that was last byte

 

This looks pretty similar to the CLI loop, but the CLCL instruction can bypass large numbers of blanks with each execution. If the entire input area is blank, you can only execute it once anyway.

There are two points to note about the way CLCL works in this example:

  1. Because the second operand length (in the three bytes of R5) is zero, the second operand address is ignored - R4 doesn’t even have to contain a valid address. (However, beware the high byte of R4 will be changed anyway, either to   the whole byte cleared to binary zero  if in 24 bit addressing mode, or just the top bit if in 31 bit addressing mode ! ) 
    The instruction uses an implied second operand, which is the same length as the first operand and consists entirely of the pad character supplied in the high byte of R5, in this case a blank. So the comparison stops when a non-blank byte is found in the first operand.
  2. CLCL (like MVCL) increments the address registers and decrements the length registers as it goes; so you can continue where you left off with only a minor adjustment to avoid finding the same non-blank character over and over again.

Incidentally, the condition code set by CLCL is the same as for CLI, so if the ‘Take appropriate action’ code uses the condition code to distinguish between values less than X’40’ and those greater than X’40’, it will still work.

Validating a packed decimal field

Assume that you have a field of length L which is supposed to be packed decimal, and you want to find out if it really is. This could be done with a loop examining each byte in turn, but there is a much neater method, and as a bonus if the field is invalid, you can tell whether it’s a sign or a digit which is wrong, and if the field is valid you can tell whether it is positive or negative.

 


         XR    R2,R2 		       Clear TRT result register
         UNPK  WORK(L*2+1),FIELD(L+1)  Put each nibble into a separate byte
         TRT   WORK(L*2),PDVAL-C'0'    Look for a sign
         BZ    BADSIGN                 Error if no sign found at all
         BL    BADDIGIT                Error if sign found before end
         BCT   R2,NEGATIVE             Branch if negative sign found at end
         B     POSITIVE                Must be positive sign at end.
WORK     DS    CL(L*2+1)
PDVAL    DC    10X'00',AL1(2,1,2,1,2,2)        

 

The above example is valid for fields up to 7 bytes long. For longer fields, the basic technique is still valid but you need more UNPK instructions at the beginning. For example, if L is between 8 and 14, the following will do:

 


         UNPK  WORK(15),FIELD(8)
         UNPK  WORD+14(L*2-13),FIELD+7(L-6)       

 

Validating a character input field.

Suppose you have a character input (e.g from a 3270 terminal) which is meant to only contain numeric digits. Assume that the field is N bytes long

 


         MVN   FZONES(N),INPUT
         CLC   FZONES(N),INPUT
         BNE   INVALID
                      ....
FZONES   DC    (N)C'0'   

 

Nice and simple and avoids another of those boring CLI loops. Of course it will not catch invalid characters in the range X’FA’-X’FF’ which aren’t likely from a vanilla 3270, but if  you have the possibility of APL keyboards then you will need to check the appropriate IBM manual to see what they can send back.

 


Errors and return codes

The next two tips will help with return code checking  and  forcing abend dumps when all else fails.

Return code checking

This  technique may be useful if the program does a lot of return code testing.

The usual way of  checking return codes is to load and test  R15 returned from a sub-routine.

 


      LTR R15,R15
      BNZ ERRRTN   +4 etc bad code
*                  +0     ok code
*      drop through to normal processing
*      if R15 is zero

 

The above two instructions can be rationalised to just the following one, but there is no such thing as a free lunch so the downside is that this will only work with odd value registers, and that the value in the register is altered so it can't be relied on to have the original value in subsequent processing.

However it does save 4 bytes each time it is used, so it is not worth doing for just one test, but may be worth it if there are a lot of tests. Do make sure you code and test carefully,  a typo of R1 instead of R15 could waste a lot of time.

 

         BXH R15,R15,ERRRTN     

 

Testing two equated values are equal

In theory it should never be necessary to have two equated variables for the same value, but it does happen from time to time so this is a simple way of ensuring an assembly error if things get out of sync.

 

       MACRO
        EQUTEST &X,&Y
.*********************************************************************
.* Test that two values are the same. this can be useful to confirm  *
.* that private copy of values from operating system macros still    *
.* hold good. Non-matching values give an assembly error.            *
.*********************************************************************
       	 DC      0SL2(&X-(&Y),&Y-(&X))
        MEND     

 

An example of this in use is shown below.

 


        *
TEMP    CSECT
ONE     EQU   1
TWO     EQU   2
LCBONE  EQU   1
        EQUTEST ONE,ONE
        EQUTEST ONE,LCBONE
        EQUTEST ONE,TWO           this should cause an assembly error
        EQUTEST LCB,LCBONE
        EQUTEST FLAG1,FLAG2
        EQUTEST FLAG3,FLAG2       and this will be another another error
CBONE   DC  CL1' '
LCB     EQU *-CBONE
FLAG1   EQU X'0F'
FLAG2   EQU X'0F'
FLAG3   EQU X'0E'
        END  

 

Forcing program checks

No doubt most people have come across the DC halfword zero trick to force a program check at a particular point, which is often done with a simple zap achieved by using AMASPZAP to alter a load module on a load library.

What is not so well known is that it is possible to provide a variation so that the code alters itself on the first time it is used and fails the second time through, or even the third time through.

 


          DC  H'0'      Fail 1st time
*
          MVI *,0       Fail 2nd time through
*
          MVI *,X'97'   Fail 3nd time through
*                       Becomes XI *,X'97' 2nd time through       

 

You might say this is a waste of time because I can step through my code with a debugger, well that is true in your own test/development environment, but is not much use in a production environment or when shipping code to run on another mainframe.

 


Lack of addressability

One of the classic problems of an assembler program that grows and grows over the years is that of addressability.

A program with a single base register can address from 0 to +4095 bytes, and for every additional base register another 4096 bytes can be addressed.

However in a maintenance situation putting in some extra code can blow the limit and there may not be a spare convenient register to use to address another 4096 bytes of program.  

The following techniques may be found useful in a desperate situation with short time scales, but where time permits a total code re-structure to remove the addressability issue is always the best solution as the following tricks only put off the day of reckoning and leave an even  bigger headache for the next person doing maintenance. Where time permits and the code is going to be in use for some time to come, then the preferred solution is always to restructure the program by breaking it down into several modules.

Remove duplicate code

The first thing to do is see if the program is suffering from having the same code inline in several places. If so then it worth seeing if the common repeated can be converted to be a subroutine called by a BAL instruction.

Save on return code checking

If there are a lot of bits of code doing return code checking then the technique above may be useful. It only save a few bytes but can be useful where there is a lot of return code checking logic.

Check the literal pool

Another thing to check when addressability has been exceeded is the literal pool, as often there will be values that are the same, but have been defined slightly, so a little rationalisation could save some storage.

Also the literal pool should be compared with program constants as someone may have defined BLANKS DC CL132' ' somewhere in the program and someone else may have added some code which used the literal =CL132' ' at a later stage, so this could save a lot of storage.

Avoid long literals with a repeated value

Having said that don't forget that code which uses a literal like =CL132' ' to initialise an print line can be replaced with a MVI/MVC pair of instructions as below to save 128 bytes.

 


         MVC LINE,=CL132' '           Generates a 132 byte literal
*
         MVI LINE,C' '                achieve the same effect with
         MVC LINE+1(L'LINE-1),LINE       128 less bytes

 


Now you've read the quick and easy hints and tips try the section on writing maintainable code, or delve a little into the world of macros.