Converting z/OS assembler code to COBOL
Introduction: Why convert to COBOL?
In today's programming, with programs written in high-level and scripting languages, it is easy to forget that a significant portion of enterprise IT function lives on big iron mainframes in the form of z/OS assembler code. You may find that your own investment in assembler applications is reaching its end and that you need to move to a higher level language, but you are not ready to jump all the way to Java™ and other newer languages. Remember that COBOL language, though not today's hottest technology under the sun (by a long shot), still offers dramatically greater flexibility and ease-of-coding than assembler language. Though there seems to be a rush to Java and other Web-enabled languages, COBOL remains the only proven 4GL language that handles large volumes of data to be processed on the mainframe.
Coding in assembler language is not like coding in any other language, because assembler lacks the flexibility and user-friendliness of a high-level language. A low-level language directly addresses memory-resident areas called registers; in any high-level language, the variables handle this function.
There are several reasons to convert a low-level assembler program to the higher level COBOL language. The main reasons are:
- There might be licensing issues with assembler code, and the code might expire. Licensing is often expensive, and you risk losing rights to use the code.
- Assembler code can be difficult to maintain because of a shortage of experienced professionals.
- Programming in assembler language is not as flexible or user-friendly as COBOL language.
This article introduces you to some of the intricacies of the assembler language and provides general guidance for how to convert assembler code to COBOL. Code samples show the same program in assembler and COBOL to illustrate the differences.
Understanding the basics of an assembler instruction
An assembler instruction is generally divided into the following four parts:
- Mnemonic (the operation to be performed)
- Operands and parameters
The label and comment are not required.
For example, consider the following instruction:
ADD-ROUTINE AR R1,R2
This means that the contents in
added, and the result is stored in
R1. The various parts are
ADD-ROUTINEis the label.
ARis the mnemonic for
R2are the operands. The contents of these registers are added, and the sum is placed in
In assembler, registers are memory locations.
Exploring the framework of an assembler program
Assembler programs generally follow a framework that consists of the following parts:
- This is an initial housekeeping routine that saves the current register contents so that the registers can be used for programming purposes in the assembler routine and restored to their original contents after the routine is completed.
- In this part of the program, the variables used in the assembler
routine are defined as
EQU, and the logic is coded.
- The assembler language uses this statement to set the location of the current base register.
DSECT(dummy section) declarations
- When other programs call the assembler routines, these declaractions within the assembler program receive the parameters of the calling programs.
Listing 1 shows a simple assembler code fragment that contains the parts of this framework. A doubleslash (//) precedes comments about instructions.
Listing 1. Assembler code fragment
HELLO CSECT USING *,15 //Make R15 and HELLO point to the current address where //this instruction is present SAVE 14,12,12(13) //Save contents in R14,R12 into address location //pointed to by (12+R13) R14, and R12 holds the return address //(the address of the next instruction to be run in the //calling program) LR 12,15 //Load register R12 with contents of R15 USING HELLO,12 WTO 'Hello world.' //Display ‘Hello world’ on console XR 15,15 //Ex-or to clear R15 RETURN (14,12) //Restore the contents of R14 and R12 END
Converting assembler to COBOL: A few tips
COBOL uses a different framework. Here are some tips to follow when converting assembler code to COBOL:
- Declare variables in the
- Code logic in the
- Ignore a
DSECT(dummy section) declarations as linkage section variables.
- Replace register names, such as
R2, with meaningful names in COBOL.
- When copying or moving blocks of data from one memory location to
another using COBOL, use the pointer concept. The pointer concept
STOREinstructions of assembler. Declare a pointer as
01 PTR1 USAGE IS POINTER.
MODEstatements in assembler to if-else conditions in COBOL.
- Do not explicitly code
DS-Declare Storagefor variables in COBOL; it is automatically handled during variable declaration.
The code from Listing 1 is converted to COBOL in Listing 2.
Listing 2. Assembler code fragment rewritten in COBOL
IDENTIFICATION DIVISION. PROGRAM-ID. HELLO. ENVIRONMENT DIVISION. DATA DIVISION. PROCEDURE DIVISION. MAIN-PARA. DISPLAY 'Hello world'. STOP RUN.
Code samples: Converting assembler to COBOL
Listing 3 shows part of a sample assembler routine called ASSEM123, and Listing 4 shows its equivalent in COBOL code. The routine creates a copy of a certain memory range (<1K) and copies a 1K chunk of data into another program-defined area. This process repeats until the entire block of data >1K is copied to the program-defined area. This is a called routine with parameters passed to it.
Note that comments to the code are surrounded by asterisks (**) or preceded by double slashes (//).
Listing 3. Assembler routine code sample ASSEM123
ASSEM123 CSECT STM R14,R12,12(R13) SAVE REGS BALR R12,0 ESTABLISH BASE REGISTER USING *,R12 ST R13,WKAREA+4 LA R13,WKAREA SPACE 3 L R6,0(R1) ESTABLISH ADDR TO BEG OF W-S USING WSSTRT,R6 L R5,4(R1) ESTABLISH ADDR TO END OF W-S USING WSEND,R5 L R8,8(R1) ESTABLISH ADDR TO W-S SAVE AREA USING WSSAVE,R8 L R4,12(R1) ESTABLISH ADDR TO W-S PARM LIST USING WSPARM,R4 MVI DONEFLG,NOTDONE SET FLAG TO NOT DONE LR R7,R5 PREPARE FOR SUBTRACTION SR R7,R6 DETERMINE LENGTH OF W-S AH R7,ONE FOR CORRECT LENGTH, SET UP 1 CH R7,FULLREC BIGGER THAN THE 1K SAVE REC BH MRECS NEED TO STORE MULTIPLE SAVE RECS LR R9,R7 SET UP FOR 1 MOVE AND SAVE STH R7,0(R8) SET LENGTH IN SAVE REC LA R8,2(R8) BUMP TGT PTR PAST LENGTH 1/2WORD MVCL R8,R6 MOVE WS TO WS SAVE REC MVI DONEFLG,DONE SET FLAG TO DONE B FINISH EXIT MRECS DS 0H CLC SAVER6,=F'00' FIRST TIME THROUGH? BE FIRST R6 OK AS IS, MUST MOVE MAX LRECL L R6,SAVER6 LOAD PTR TO NEXT WS AREA TO MOVE LR R7,R5 R5 POINTS TO WS END, CALC SR R7,R6 DETERMINE LENGTH OF WS TO MOVE AH R7,ONE SET UP 1 FOR PROPER LENGTH CH R7,FULLREC REMAINING WS <= SAVE REC SIZE? BNH CONT1 YES...MOVE ACTUAL WS LENGTH FIRST LH R7,FULLREC NO...LOAD MAX REC SIZE CONT1 LR R9,R7 LOAD ODD RECS WIH MOVE LENGTH STH R7,0(R8) SET LENGTH IN SAVE REC LA R8,2(R8) BUMP TGT PTR PAST LENGTH MVCL R8,R6 MOVE WS TO WS SAVE REC AR R6,R7 BUMP SOURCE PTR BY MOVE LENGTH ST R6,SAVER6 SAVE FOR NEXT CALL CR R6,R5 MOVED ALL WS? BL FINISH NOT YET...EXIT MVI DONEFLG,DONE SET FLAGE TO DONE B FINISH AND EXIT EJECT FINISH DS 0H XR R15,R15 ZERO TO RETURN CODE REG L R13,WKAREA+4 RESTORE BACK PTR LM R14,R12,12(R13) RESTORE REGS BR R14 RETURN TO CALLER SPACE 3 //Defining all the variables used in the assembler routine WKAREA DS 18F REG SAVE AREA FULLREC DC H'0998' MAX REX SIZE FOR WS SAVE REC ONE DC H'0001' MAX REX SIZE FOR WS SAVE REC SPACE 1 NEWSLEN DC F'00000000' NEW WS LENGTH SAVE AREA WSACUM DC F'00000000' OLD WS ACCUMULATOR ON RESTORE WTOMSG WTO 'U0001-** RSAM ABEND - CHANGE IN WORKING STORAGE SIZE',X ROUTCDE=11,MF=L SPACE 1 //Here 4 DSECTS are given that correspond to 4 parameters //sent to the corresponding COBOL code //(these are captured in the LINKAGE SECTION of the COBOL code) WSSTRT DSECT WORKING-STORAGE START ADDR DS F WSEND DSECT WORKING-STORAGE END ADDR DS F WSSAVE DSECT WORKING-STORAGE SAVE RECORD DS F WSPARM DSECT WORKING-STORAGE SAVE MODE MODE DS CL1 SAVER6 DS F R6 SAVE AREA DONEFLG DS CL1 TELLS IF ALL OF WS WAS MOVED DONE EQU C'D' DONE MOVE NOTDONE EQU C'N' NOT DONE MOVE ERROR EQU C'E' WS LENGTH HAS CHANGED ON RESTART SPACE 1 R1 EQU 1 PARAMETER LIST ADDRESS R3 EQU 3 WORK REGISTER R4 EQU 4 PARAMETER LIST ADDRESS R5 EQU 5 PARAMETER LIST ADDRESS R6 EQU 6 W-S PARM ADDR R7 EQU 7 W-S START ADDR R8 EQU 8 W-S END ADDR R9 EQU 9 W-S SAVE RECORD ADDR R10 EQU 10 SAVE REG FOR W-S START ADDR R11 EQU 11 R12 EQU 12 MODULE BASE REG R13 EQU 13 R14 EQU 14 LINK REGISTER R15 EQU 15 BRANCH REGISTER SPACE 3 END
Because the assembler routine is a called routine, a driver program is
simulated to test the COBOL equivalent. This driver passes the four
parameters to the COBOL code equivalent to the ones used in the assembler
routine. Listing 4 shows that the driver passes a 1K chunk of data named
WS-DISPAREA to the equivalent COBOL code.
Listing 4. Assembler driver program code fragment
IDENTIFICATION DIVISION. PROGRAM-ID. DRIVER1. ENVIRONMENT DIVISION. DATA DIVISION. WORKING-STORAGE SECTION. 01 WS-PTR1 USAGE POINTER. 01 WS-PTR2 USAGE POINTER. 01 WS-INDEX PIC 9(2) VALUE ZEROS. 01 WSSAVE-PARM-AREA. 05 WSSAVE-PARM PIC X VALUE 'S'. 05 WSSAVE-INDEX PIC S9(8) COMP SYNC VALUE +0. 05 WSSAVE-RETURN-CODE PIC X VALUE 'N'. 01 WSSAVE-R POINTER. LINKAGE SECTION. 01 WS-DISPAREA PIC X(1000) PROCEDURE DIVISION USING WS-DISPAREA. MAIN-PARA. //SETS THE POINTERS TO THE START AND END OF WS-DISPAREA .HERE //ADDRESSABILITY IS GIVEN TO THESE POINTER VARIABLES BY POINTING //IT TO WS-DISPAREA.WS-DISPAREA SHOULD ALWAYS BE DEFINED IN THE //LINKAGE SECTION ELSE AN ADDRESSABILITY ERROR WILL BE SHOWN SET WS-PTR1 TO ADDRESS OF WS-DISPAREA. SET WS-PTR2 TO ADDRESS OF WS-DISPAREA(999:1) CALL PGM1 USING WS-PTR1 WS-PTR2 WSSAVE-PARM-AREA WSSAVE-R. END-CALL. STOP RUN.
Listing 5 shows the assembler code from Listing 4 converted to COBOL. Note that the driver program with the four parameters calls PGM1, the COBOL code equivalent to that used in the assembler routine. The length of the data passed is calculated and then copied into a program-defined area.
Listing 5. Assembler routine code sample ASSEM123 rewritten in COBOL
IDENTIFICATION DIVISION. PROGRAM-ID. PGM1. ENVIRONMENT DIVISION. DATA DIVISION. WORKING-STORAGE SECTION. 01 WORK-RECORD PIC X(100) VALUE SPACES. 01 SUBSCR PIC 9(5) VALUE ZERO. 01 LOOP-COUNTER PIC 9(5) VALUE ZERO. 01 WS-REMAINDER PIC 9(5) VALUE ZERO. 01 LOOP-EXIT PIC 9(1) VALUE ZERO. 01 WS-LENGTH PIC 9(5) VALUE ZERO. 01 WS-IDX PIC 9(5) VALUE 1. 01 WS-IDX2 PIC 9(5) VALUE 1. 01 WS-POINTER POINTER. LINKAGE SECTION. 01 START-ADDRESS-POINTER POINTER. 01 END-ADDRESS-POINTER POINTER. 01 WS-END PIC X(1). 01 WS-PARM. 05 WS-PARM1 PIC X. 05 WS-INDEX1 PIC S9(8) COMP SYNC. 05 WS-RETURN-CODE PIC X. 01 WS-AREA. 05 MY-ARRAY PIC X(1) OCCURS 1000 TIMES. 01 WSSAVE-R1. 05 WSSAVE-R12 PIC X(1) OCCURS 100 TIMES. PROCEDURE DIVISION USING START-ADDRESS-POINTER END-ADDRESS-POINTER WS-PARM WSSAVE-R1. MAIN-PARA. IF WS-PARM1 EQUAL 'S' AND WS-RETURN-CODE EQUAL 'N' DISPLAY 'START-ADDRESS-POINTER=' START-ADDRESS-POINTER DISPLAY 'END-ADDRESS-POINTER=' END-ADDRESS-POINTER SET ADDRESS OF WS-AREA TO START-ADDRESS-POINTER DISPLAY 'WS-AREA=' WS-AREA SET ADDRESS OF WS-END TO END-ADDRESS-POINTER ******************************************************************* //BLOCK TO CALCULATE THE LENGTH OF THE WORKING-STORAGE //SECTION PASSED ******************************************************************* PERFORM UNTIL LOOP-EXIT = 1 IF MY-ARRAY ( WS-IDX ) NOT = WS-END COMPUTE WS-LENGTH = WS-LENGTH + 1 END-COMPUTE ELSE SET WS-POINTER TO ADDRESS OF MY-ARRAY(WS-IDX) IF WS-POINTER = END-ADDRESS-POINTER MOVE 1 TO LOOP-EXIT END-IF COMPUTE WS-LENGTH = WS-LENGTH + 1 END-COMPUTE END-IF ADD 1 TO WS-IDX END-PERFORM DISPLAY 'LENGTH OF AREA PASSED=' WS-LENGTH ******************************************************************** //COPIES THE WORKING-STORAGE SECTION PASSED VIA WS-DISPAREA //OF THE DRIVER INTO WS-AREA. FROM WS-AREA, DATA IS COPIED TO //WORK RECORD AND FROM WORK-RECORD TO THE WSSAVE-R12 //ARRAY, WHICH IS 100 BYTES LONG. WORK RECORD ALSO HOLDS //ONLY 100 BYTES OF DATA THUS TRANSFERRING THE DATA TO //WSSAVE-R12 ******************************************************************** DIVIDE WS-LENGTH BY 100 GIVING LOOP-COUNTER REMAINDER WS-REMAINDER END-DIVIDE MOVE 1 TO SUBSCR PERFORM LOOP-COUNTER TIMES MOVE WS-AREA(SUBSCR:100) TO WORK-RECORD DISPLAY 'WORK-RECORD=' WORK-RECORD MOVE WORK-RECORD TO WSSAVE-R12(WS-IDX2) COMPUTE SUBSCR = SUBSCR + 10 END-COMPUTE COMPUTE WS-IDX2 = WS-IDX2 + 10 END-COMPUTE END-PERFORM IF WS-REMAINDER NOT = ZERO MOVE WS-AREA(SUBSCR:WS-REMAINDER) TO WORK-RECORD MOVE WORK-RECORD TO WSSAVE-R12(WS-IDX2) END-IF END-IF. MOVE 'D' TO WS-RETURN-CODE. DISPLAY WS-RETURN-CODE. EXIT PROGRAM.
- Introduction to the New Mainframe: z/OS Basics: Refer to this IBM Redbook™ that provides the background knowledge and skills necessary to begin using the basic facilities of a mainframe computer. This redbook is the first in a series of textbooks designed to introduce mainframe concepts and to prepare students for a career in large-systems computing.
- IBM trial software: Build your next development project with software for download directly from developerWorks.