Investigate the ILE CL compiler's mechanism for receiving program parameters.
There's no doubt that the program-call mechanism in IBM i is fully dynamic. The called program can be determined at run time instead of at compile time. The parameter list passed to the called program can be composed at run time by the calling program. Also, the length of the parameter list for a called program can be changed dynamically. As you might know, an ILE RPG program can retrieve of the length of the parameter list via the RPG %PARMS built-in function (BIF). An ILE C program can also achieve this goal via the first argument of its main function, int argc. So is it possible for an ILE CL program to know the number of parameters passed to it? This article will lead you to answer, all the way down to the RISC instruction level.
Two fields in the program header of an MI program object determine the Number of parameters attribute of the program: PEP MIN PARMS and PEP MAX PARMS, meaning the minimum and maximum number of parameters that can be passed to the program entry procedure (PEP) of program. Upon a program-call (due to a Call External (CALLX), a Transfer Control (XCTL), or a Call Program with Variable Length Argument List (CALLPGMV) MI instruction), if the length of the parameter list passed to the called program is out of the range from PEP MIN PARMS to PEP MAX PARMS of the called program, a hex 0802 (Argument List Length Violation) exception will be signaled. Different compilers choose different policies in determining the range of the allowable parameter list length. In OMI, the programmer is responsible for specifying the minimum and maximum number of parameters of an OMI program. Additionally, the calling program can set the length of the parameter list to a called program via the Set Argument List Length (SETALLEN) OMI instruction, and the called program can retrieve the number of parameters passed to it via the Store Parameter List Length (STPLLEN) OMI instruction. Experiments show that for an OPM RPG or an OPM COBOL program, the minimum number of parameters is set to zero, and the maximum number of parameters is set to the length of the program-defined parameter list. For an ILE program, the length of the parameter list is determined by the entry point procedure of the PEP module of the program. For example, the parameter list length of an ILE COBOL module ranges from zero to the number of parameters of the program-defined parameter list, and that of an ILE CL module ranges from zero to 255. Therefore, a calling program is able to call an ILE CL program (or more exactly, an ILE program the PEP module of which is an ILE CL module) and pass up to 255 parameters even though the called program does not expect any parameter. This makes it more important for an ILE CL program to know the exact number of parameters passed to it.
The OPM Parameter Count (_OPM_PARM_CNT) System Built-In
According to the documentation of the_OPM_PARM_CNT system built-in in the IBM i
However, the same documentation shows that a user program written in any ILE High-Level Language(s) (HLL) cannot utilize the _OPM_PARM_CNT system built-in: This function can only be used by procedures defined to be a program entry procedure. Otherwise, an instruction stream not valid (hex 2A1B) exception will be signaled during module creation. As you might know, ILE compilers generate an entry point procedure into each compiled module object. (A DSPMOD DETAIL(*PROCLIST) command will show you that.) Only the entry point procedure of a module can act as the PEP of an ILE program; therefore, a user procedure in a compiled ILE program never has the chance to become the PEP of the program.
The NPM Procedure Parameter List Address (_NPMPARMLISTADDR) System Built-In
The documentation of the_NPMPARMLISTADDR system built-in in the IBM i
The address of the New Program Model parameter list received by the current invocation is returned. ... Arguments is a variable length field (at offset hex 20) used to pass argument values to the current invocation.
Can we use the _NPMPARMLISTADDR system built-in in the main procedure of an ILE CL program to retrieve the number of parameters passed? The following is a tiny CL program, parm05.clle, that displays the first 96 bytes of the arguments field in the parameter area returned by _NPMPARMLISTADDR.
PGM PARM(&A &B &C) DCL VAR(&A) TYPE(*CHAR) LEN(1) DCL VAR(&B) TYPE(*CHAR) LEN(1) DCL VAR(&C) TYPE(*CHAR) LEN(1) DCL VAR(&PRMARA@) TYPE(*PTR) DCL VAR(&CNT) TYPE(*INT) LEN(4) VALUE(0) DCL VAR(&PRMCNT) TYPE(*CHAR) STG(*DEFINED) + LEN(4) DEFVAR(&CNT) DCL VAR(&ARG@) TYPE(*PTR) DCL VAR(&PRMARA) TYPE(*CHAR) STG(*BASED) + LEN(128) BASPTR(&PRMARA@) DCL VAR(&ARG) TYPE(*CHAR) STG(*BASED) LEN(16) + ASPTR(&ARG@) CALLPRC PRC('_NPMPARMLISTADDR') RTNVAL(&PRMARA@) CHGVAR VAR(&ARG@) VALUE(%ADDR(&PRMARA)) CHGVAR VAR(%OFS(&ARG@)) VALUE(%OFS(&PRMARA@) + 32) DOWHILE COND(%OFS(&ARG@) *LT (%OFS(&PRMARA@) + 128)) SNDPGMMSG MSGID(ART0102) MSGF(ARTMSG) MSGDTA(&ARG) CHGVAR VAR(%OFS(&ARG@)) VALUE(%OFS(&ARG@) + 16) ENDDO SNDPGMMSG MSGID(ART0101) MSGF(ARTMSG) MSGDTA(&PRMCNT) ENDPGM |
Note that the program-defined parameter list of PARM05 takes three parameters. Note also that the &CNT and &PRMCNT variables and the SNDPGMMSG MSGID(ART0101) MSGF(ARTMSG) MSGDTA(&PRMCNT) command are for the experiment at the end of this article.
Prepare a message file that is needed by PARM05 like this:
CRTMSGF MSGF(ARTMSG) ADDMSGD MSGID(ART0101) MSGF(ARTMSG) MSG('Totally &1 parameters passed.') FMT((*UBIN 4)) ADDMSGD MSGID(ART0102) MSGF(ARTMSG) MSG('&1') FMT((*HEX 16)) |
Call program PARM05 with three parameters. The content of the arguments field returned by _NPMPARMLISTADDR might look like the following:
4 > call parm05 (a b c) X'8000000000000000FC19EC7E44001646' X'8000000000000000FC19EC7E44001667' X'8000000000000000FC19EC7E44001688' X'00000000000000000000000000000000' X'0100000000000060151081087FFF8020' X' |
The content of the arguments field contains three space pointers addressing the parameters passed to PARM05. It shows that _NPMPARMLISTADDR is able to return all the program-defined parameters.
Call program PARM05 again with five parameters. The content of the arguments field might look like the following:
4 > call parm05 (a b c d e) X'8000000000000000FC19EC7E44001646' X'8000000000000000FC19EC7E44001667' X'8000000000000000FC19EC7E44001688' X'00000000000000000000000000000000' X'0100000000000060151081087FFF8020' X' |
The fourth and fifth parameters passed to PARM05 are not returned by _NPMPARMLISTADDR. Does this mean that the _NPMPARMLISTADDR system built-in cannot be used to retrieve the parameters passed to the main procedure of an ILE CL program? Or the main procedure (user procedure) of an ILE CL program does not receive the full parameter list passed by the calling program? This question will be answered later in this article by investigating the actual PEP of an ILE CL program, _CL_PEP.
Finally, call program PARM05 with two parameters. The content of the arguments field might look like the following:
4 > call parm05 (a b) X'8000000000000000FC19EC7E44001646' X'8000000000000000FC19EC7E44001667' X'00000000000000000000000000000000' X'00000000000000000000000000000000' X'0100000000000060151081087FFF8020' X' |
The content of the arguments field contains two space pointers addressing the parameters passed to PARM05, followed by 16-byte hex 00. The following information is supplied in the Passing parameters section of the CL Programming book, which is available in the IBM i
When calling an ILE program or procedure, the operating system does not check the number of parameters that are passed on the call. In addition, the space where the operating system stores the parameters is not reinitialized between program or procedure calls. Calling a program or procedure that expects n parameters with n-1 parameters makes the system use whatever is in the parameter space to access the nth parameter. The results of this action are very unpredictable. This also applies to programs or procedures written in other ILE languages that call CL programs or procedures or are called by CL programs or procedures.
According to this documentation, the 16 bytes at offset hex
Investigate the _CL_PEP Procedure
_CL_PEP is the entry point procedure generated by the ILE CL compiler for each CL module. When a CL module is chosen as the PEP module of an ILE program (due to a CRTPGM command or a CRTBNDCL command), the _CL_PEP procedure of the CL module becomes the program entry procedure (PEP) of the program. The following is the PowerPC instruction stream of the _CL_PEP procedure (at VRM540) of the above-mentioned ILE CL program PARM05. The _CL_PEP operates on the parameter list passed to the program and then passes control to the user procedure PARM05. The operations on the parameter area are explained by pseudocode and comments.
RISC INSTRUCTIONS (_CL_PEP) LOCATION OBJECT TEXT SOURCE STATEMENT Comments/Pseudocode ... ... 000040 607E0000 ORI 30,3,0 [1] ... ... 000094 3B000000 ADDI 24,0,0 000098 931FFF40 STW 24,0XFF40(31) index = 0 0000B0 935FFF40 STW 26,0XFF40(31) Save the increased value of index back to (automatic) program storage 0000B4 0000B8 418D0090 BC 12,13,0X90 if index > num_parms_defined; then goto end_loop // [4] 0000BC 831FFF40 LWZ 24,0XFF40(31) 0000CC 833FFF40 LWZ 25,0XFF40(31) 0000D0 3B59FFFF ADDI 26,25,-1 0000D4 7B 0000D8 7B5B26E4 RLDICR 27,26,4,59 0000DC 3B1FFF50 ADDI 24,31,-176 parm1_ptr_addr = r31 - 0xB0 // [5] 0000E0 0000E4 7FD 0000E8 835FFF40 LWZ 26,0XFF40(31) 0000EC 837E0004 LWZ 27,0X4(30) 0000FC 000100 7B5B 000104 000108 E 000110 41D58023 BCLA 14,21,0X8020 000114 4BFFFF90 B 0X3FFFF90 goto loop // Handle the next parameter passed to the program 000118 831FFF40 LWZ 24,0XFF40(31) not_passed_parm: 000120 7B7B0020 RLDICL 27,27,0,32 000124 7B 000128 3B3FFF50 ADDI 25,31,-176 parm1_ptr_addr = r31 - 0xB0 // [5] 000130 7FD 000134 3B600000 ADDI 27,0,0 000138 FB780000 STD 27,0X0(24) 000140 41D58023 BCLA 14,21,0X8020 000144 4BFFFF60 B 0X3FFFF60 goto loop 000148 3B5FFF50 ADDI 26,31,-176 end_loop: 000150 E2DA 000154 FAD90022 STQ 22,0X20(25) 000158 E2DA 000160 E2DA 000164 FAD90042 STQ 22,0X40(25) 000168 63230000 ORI 3,25,0 [11] 000170 48000091 BL 0X90 call-procedure PARM05 // [12] ... ... |
Notes
[1] From Chapter 33: Analysis of SCV 7, Program Call of Leif Svalgaard's e-book AS/400 Machine Level Programming, we know that the address of the parameter area to a called program during a program call is passed via General Purpose Register (GPR) 3 (r3). Later, you'll find out that r3 is also used to pass the address of the parameter area during a procedure call. Here, the content of r3 is copied to r30.
[2] Load the number of parameters passed into r27 from the address of the parameter area (now stored in r30) at offset 4. By debugging an OMI program, you learn that during a program call the number of parameters and each individual parameter are stored in the parameter area for the called program as the following:
- A BIN(4) field at offset 4 is the number of parameters passed.
- 8-byte Single-Level Store (SLS) addresses of all parameters are stored in the parameter area sequentially starting from offset 8. By convention, parameters for a program call are passed by references, which means each 8-byte address in the parameter area addresses a space pointer to the corresponding parameter.
[3] Start of the parameter-handling loop.
[4] num_parms_defined is the number of the parameters in the parameter list defined by the program. In this example, the value of num_parms_defined is 3. As shown by the program logic, parameters whose index numbers are greater than the length of the program-defined parameter list are simply ignored by _CL_PEP. Therefore the _NPMPARMLISTADDR system built-in issued in the main procedure of an ILE CL program cannot retrieve any parameters other than the program-defined parameters.
[5] As you might know, r31 always addresses the upper limit of the automatic storage frame (ASF) of the current invocation. The ADDI 24,31,-176 instruction locates the address of the space pointer to the first program-defined parameter in the ASF of _CL_PEP.
[6] Here, parm_ptr_addr means the address of the space pointer to the current parameter in the ASF of _CL_PEP. parm_ptr_addr is computed by adding (index - 1) * 16 to the address of the space pointer to the first program-defined parameter. 16 is the size of an MI pointer.
[7] addr is set to the 8-byte SLS address at offset (8 * index) from the beginning of the parameter area.
[8] The space pointer addressing the index parameter is copied from address addr to the ASF location where the space pointer to the index program-defined parameter is stored.
[9] For each parameter that is not passed by the caller program, the space pointer addressing the corresponding parameter is set to 16-byte hex 00. Therefore, in the main procedure of an ILE CL program, you can test whether a specific parameter is passed by testing the address of the parameter via any method that can be used to test for a null pointer—for example, the Compare Pointer Type (CMPPTRT) MI instruction with the comparison value set to hex 00.
[10] Before _CL_PEP passes the control to the user procedure PARM05, the space pointers to the program-defined parameters recorded by _CL_PEP are copied to the parameter area for procedure PARM05, which can be retrieved via the _NPMPARMLISTADDR system built-in in procedure PARM05. In this example, (r31 - 0x60) is the address of the parameter area for procedure PARM05. PARM05 takes three program-defined parameters. The corresponding space pointers are copied from (r31 - 0xB0) to location (r31 - 0x60 + 0x20).
[11] r3 is set to the address of the parameter area (r31 - 0x60) for procedure PARM05.
[12] Finally, call procedure PARM05.
The Final Experiment: Display the Number of Parameters Passed to an ILE CL Program
From the previous analysis of the _CL_PEP procedure of an ILE CL program, we know that the compiler-generated procedure knows the number of parameters passed to the program very well; it just doesn't pass this information to the user procedure of the CL program. In the following experiment, let's make a tiny modification to the RISC instruction stream of the user procedure of ILE CL program PARM05 to retrieve the number of parameters passed to the program, which was omitted by the _CL_PEP procedure.
Observe the RISC instructions generated for _CL_PEP again. You'll find that the content of r30 (which is set to the value of r3 at the beginning of _CL_PEP) remains unchanged until procedure PARM05 is invoked. Therefore, we may have the chance to obtain the address of the parameter area of the program stored in r30. Look at the start of the RISC instructions of the PARM05 procedure:
RISC INSTRUCTIONS (PARM05) LOCATION OBJECT TEXT SOURCE STATEMENT Comments ... ... 000040 607E0000 ORI 30,3,0 Copy r3 to r30 000044 3B600000 ADDI 27,0,0 000048 937FFD74 STW 27,0XFD74(31) Initialize CL variable &CNT to zero |
Change the two PowerPC instructions at offset hex 40:
LOCATION OBJECT TEXT SOURCE STATEMENT Comments 000040 837E0004 LWZ 27,0X4(30) Load the number of parameters passed to the program from r30 at offset 4 000044 607E0000 ORI 30,3,0 The original instruction at offset hex 40 000048 937FFD74 STW 27,0XFD74(31) Store the number of parameters passed to the program into CL variable &CNT |
Now, call the modified PARM05 program with different numbers of parameters to test the modification. For example, CALL PARM05 (A B C D E), the output might look like the following:
4 > CALL PARM05 (A B C D E) X'8000000000000000FC19EC7E44001646' X'8000000000000000FC19EC7E44001667' X'8000000000000000FC19EC7E44001688' X'00000000000000000000000000000000' X'01000000000000600000000000000000' X' Totally 5 parameters passed. |
Yes, we've managed to get the number of parameters passed to our ILE CL program! However, it is only an experiment rather than a practical solution. What we actually need is a built-in support for retrieving the length of the parameter list passed to a CL PEP. What about a %PARMS built-in in future improvements to CL? Maybe, and you've already seen how easy it is to implement such a built-in for CL.
LATEST COMMENTS
MC Press Online