Home > Coding, Style > Running a control block chain…

Running a control block chain…

Disclaimer. I didn’t invent this. I saw the general technique many years ago in some code I was working on and I have always thought it was pretty neat, so it’s time to pass it on to the probably few people left in the world that have not seen it.

Lets assume you have a control block structure like this:

CB_Struct

WSA_FIRST@ is a full word pointer somewhere in storage (working storage for example), addressed by some register so we have a using on it can can address it by label. It points to the first control block in the structure, or contains zero if there are none on the chain.

The control blocks are mapped by this DSECT:

CB       DSECT
CB_LENGTH DS F        Control block length
CB_NEXT@ DS A         Address of next cb or zero if last
         :            Other fields as required

Assuming I am using register R08 to address the control blocks, this is how I would typically run this chain:

         USING  CB,R08         
         LA    R08,WSA_FIRST@-(CB_NEXT@-CB) 
LOOP     DS    0H
         ICM   R08,15,CB_NEXT@   First, next or zero if end
         JZ    LOOPEND
         :                       Code here to process the control block
         J     LOOP
LOOPEND  DS    0H
         DROP  R08                Drop CB using

The LA (Load Address) sets the control block base register, R08, to point to some point BEFORE the address of the WSA_FIRST@ field. The resultant address does not even have to be addressable by the code because the instruction is just calculating an address of some storage location, not actually referencing it. So, NO S0C4!

In this example R08 would end up pointing to an address 4 bytes BEFORE the location of WSA_FIRST@ because the CB_NEXT@ field is at offset 4 in the control block (CB_NEXT@-CB) and we subtract that from the address of WSA_FIRST@.

So now comes the clever part.

The assembler thinks the control block (CB DSECT) is addressed by R08, and it thinks the CB_NEXT@ field is at offset +4 in the CB DSECT so the label  (CB_NEXT@) in the ICM (Insert Characters under Mask) instruction resolves to R08 plus 4. So the ICM gets the 4 bytes from where R08 is pointing (remember, it is pointing to 4 bytes BEFORE the actual first control block address field in storage somewhere) plus 4, which means on the first time through the loop it loads R08 with the data from WSA_FIRST@.

So now, R08 is either zero, if there are no control blocks on the chain, or it addresses the first control block (CB1).

If it is zero, the JZ (Jump if Zero) instruction causes us to exit the loop, otherwise we drop through to process the control block before jumping back to the start of the loop.

This time, R08 is pointing at CB1 so the ICM instruction loads R08 from the CB_NEXT@ address field in CB1 so now R08 is pointing at CB2.

Again we process the control block before jumping to the top of the loop.

This time, R08 is pointing at CB2 so the ICM instruction loads R08 from the CB_NEXT@ address field in CB2 so now R08 is pointing at CB3.

Again we process the control block before jumping to the top of the loop.

This time, R08 is pointing at CB3 so the ICM instruction loads R08 from the CB_NEXT@ address field in CB3. Since CB3 is the last control block on the chain its CB_NEXT@ field is zero so the JZ instruction is true and we exit the loop.

Using the IBM Assembler Toolkit Macros

The following is functionally equivalent, and in my opinion, a lot cleaner and easier to read (you also don’t have to worry about label names just to implement logic):

         USING  CB,R08 
         LA    R08,WSA_FIRST@-(CB_NEXT@-CB) 
         DO    WHILE=(ICM,R08,15,CB_NEXT@,NZ)
             :                      Process the control block here
         ENDDO
         DROP  R08

There is however one slight difference in how the code assembles between the DO/ENDDO version and the hard coded version I showed initially.

Even though the ICM instruction and it’s operands are coded on the WHILE operand of the DO macro, the actual instruction is generated at the END of the loop by the ENDDO macro. In addition it generates a JNZ (Jump if Not Zero) instruction to return to top of the loop, versus the JZ (Jump if Zero) instruction in my hard coded loop.

However the biggest impact of this is that you must have addressability to the control block when the ENDDO macro is invoked. So something like this will not assemble:

         USING  CB,R08 
         LA    R08,WSA_FIRST@-(CB_NEXT@-CB) 
         DO    WHILE=(ICM,R08,15,CB_NEXT@,NZ) 
             : Process the control block here
             DROP R08
         ENDDO

When the ENDDO generates the ICM instruction, the assembler no longer has addressability to the CB DSECT addressed by R08 so the assembly fails.

Running a chain backwards

The same general technique works when running a chain backwards, that is from the last entry to the first.

Obviously the anchor point field in storage has to point to the LAST control block on the chain and each control block has to have a back pointer to the control block BEFORE it (CB_PREV@ maybe) on the chain.

 

 

Categories: Coding, Style
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: