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. |
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.
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:
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.
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)
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.
The next two tips will help with return code checking and forcing abend dumps when all else fails.
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
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
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.
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.
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.
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.
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.
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.