The _LSPCO system built-in accepts a space pointer as its input and returns the origin of addressable storage of the "space" addressed by the space pointer.
In my previous article, A More Complete View of the Machine Interface of IBM i, I introduced a technique that Gene Gaunt invented to retrieve all system built-ins supported by an IBM i release. You might have noticed that many of the system built-ins retrieved via Gene's technique have never been documented publicly. One of the undocumented system built-ins is _LSPCO.
I infer that the name of the _LSPCO system built-in (which is also an NMI instruction) stands for Load Space Origin. The _LSPCO system built-in accepts a space pointer as its input and returns the origin (the beginning address) of addressable storage of the "space" addressed by the space pointer. The "space" can be an MI space object, the automatic stack of an MI thread, the static storage of an activation group, a pointer-based heap space allocated for an activation group, or the Teraspace of an MI process. _LSPCO is an unblocked system built-in, which means that, if you know the correct prototype of it, you can utilize this system built-in in your ILE high-level language (HLL) programs.
This article will lead you to the _LSPCO system built-in and show you how to utilize it in your HLL programs.
The Prototype of _LSPCO
Issuing a blocked MI instruction from a program when the thread is running in user state will raise a hex 4401 (Object Domain or Hardware Storage Protection Violation) exception (aka MCH6801). A user program that invokes a blocked system built-in will also be stopped by a hex 2A1B (Instruction Stream Not Valid) exception with reason code 22 (which means A blocked built-in function is called). _LSPCO is not a blocked system built-in, so you can utilize it in your user programs written in an ILE HLL.
I tried a few times to find out the prototype of the _LSPCO system built-in. _LSPCO accepts a space pointer (identifying the space) passed by value as its only input parameter and returns the origin of the space via a space pointer.
The prototypes of _LSPCO declared in ILE C and ILE RPG are the following, respectively:
ILE C
# pragma linkage(_LSPCO, builtin) void* _LSPCO(void*); |
ILE RPG
d lspco pr * extproc('_LSPCO') d spp * value |
How Does _LSPCO Work?
The following is the source of an ILE C program (lspco02.c) that invokes the _LSPCO system built-in to retrieve the origin (the beginning address) of the automatic stack of the current thread and then print the returned address to stdout.
# include <stdlib.h> # include <string.h> # include <stdio.h> # include <mih/cvthc.h>
# pragma linkage(_LSPCO, builtin) void* _LSPCO(void*);
# pragma linkage(_MODASA, builtin) void *_MODASA(unsigned);
static void *p = NULL;
void func() { p = _LSPCO(p); // stmt 1 }
int main() { char addr[33] = {0}; p = _MODASA(0x100); func(); cvthc(addr, &p, 32); printf("SPP: %s\x25", addr); return 0; } |
Try choosing different storage models (SLS or Teraspace) for the compiled program via the STGMDL parameter of the CRTBNDC command and observe the output of program LSPCO02.
The following are the NMI instructions generated (at VRM540) for the only statement of the func() procedure:
OFFSET OPCODE LOD1 OPERAND 1 2 OFFSET 000000B4 OPCODE PALI OPERAND 1 3 OPERAND 2 2 OFFSET OPCODE LSPCO OFFSET OPCODE STR1 OPERAND 1 2 |
As you might have noticed, the _LSPCO system built-in shares the same name as its corresponding NMI instruction. That's probably why the naming convention applied to _LSPCO is so different from the naming convention of the majority of the MI instructions we are familiar with.
Note that the PowerPC instructions generated for the _LSPCO system built-in do not involve invocation of any LIC routine; they are just inlined PowerPC instructions generated into the user code of the result program. The PowerPC instructions generated (at VRM540) for the p = _LSPCO(p); statement might look like the following:
LOCATION OBJECT TEXT SOURCE STATEMENT Notes ----- ----- ----- ----- 000020 E1040006 LQ 8,0X0(4),6 [1] 000024 000028 79472720 RLDICL 7,10,4,60 [2] 000030 41CA0010 BC 14,10,0X10 [2] 000034 794601E4 RLDICR 6,10,0,39 [2.A] 000038 E 000040 000044 E 000048 78CC01E5 RLDICR. 12,6,0,39 [4] 000050 000054 E8E08178 LD 7,0X8178(0) [3] 000058 79052B1E SELRR 5,8,5,38 [4] 000060 000064 78E52B1E SELRR 5,7,5,38 [5] 000068 |
Notes
[1] The 16-byte input space pointer is loaded into General Purpose Register (GPR) 8 and GPR 9 by the LQ instruction. (I will refer to GPR n simply as rn in the remaining portion of this article.) The SELRI instruction checks the input pointer for validity (if the MI pointer tag bits are set) and sets r10 correspondingly. r10 is set to all hex 00 if it is an invalid pointer (the MI pointer tag bits are off); otherwise, it is set to the 8-byte address portion of the pointer. (The higher 8 bytes of a 16-byte MI space pointer are the pointer type bytes and the lower 8 bytes are the address portion.)
[2] Check for Teraspace address by checking the higher 4 bits of the address portion (stored in r10). If the higher 4 bits are equal to hex 9 (which means the input space pointer addresses the Teraspace storage), the execution is branched to the RLDICR 6,10,0,23 instruction at offset hex 000040 (see [2.B]).
[2.A] For a single-level store (SLS) space pointer, the higher 5 bytes of the address portion (the segment ID) plus hex 000000 are the start of the SLS segment. The RLDICR 6,10,0,39 instruction loads the address of the beginning of the SLS segment into r6. At the beginning of a SLS segment is the 32-byte segment header, the 8-byte field at offset hex 18, which is the address of the associated space. For the base (and the only) segment of an MI space object or a segment allocated for automatic storage, static storage, or heap storage, the associated space field of the segment header is the origin of the addressable storage of the SLS segment.
[2.B] For a Teraspace address, the higher 3 bytes of the pointer's address portion plus hex 0000000000 (the origin of the Teraspace) are placed into r6 as the address portion of the space pointer to return.
[3] Load the 8-byte pointer type values for a SLS space pointer (hex 8000000000000000), an invalid pointer (hex AF00000000000000), and a Teraspace space pointer (hex 4000000000000000) into r5, r8, and r7 respectively.
[4] The RLDICR. 12,6,0,39 instruction and the SELRR 5,8,5,38 instruction set the result pointer type bytes to r8 (hex AF00000000000000) if the higher 5 bytes of r6 (address portion of the result pointer) is hex 0000000000; otherwise, the resulting pointer type bytes are set to r5 (hex 8000000000000000). In other words, if the higher 5 bytes of a space pointer is hex 0000000000, it is regarded as an invalid pointer.
[5] The higher 4 bits of r6 (address portion of the space pointer to return) is stored in the lowest 4 bits of r9 by the RLDICL 9,6,4,60 instruction, and the other bits of r9 are cleared. r9 is then compared with the value hex 9 by the CMPLI 0,1,9,9 instruction. If r9 is equal to hex 9, the 8-byte pointer type of the Teraspace space pointer (hex 4000000000000000) stored in r7 is selected for the resulting pointer type bytes (in r5); otherwise, the content of r5 remains unchanged.
[6] The resulting pointer type bytes (in r5) are stored in r8, and the resulting address (in r6) of the origin of the space is stored in r9 by the ORI 8,5,0 and ORI 9,6,0 instructions, respectively.
[7] The SETTAG and STQ 8,0X0(4) instructions finally store the 16-byte space pointer (in r8 and r9) to user storage. Note that in the above-shown example C program, space pointer p (which is a static variable) is either used as the operand of _LSPCO or is used to receive the return value of _LSPCO. So the returned space pointer is stored in the same place where the input space pointer is loaded: the start of the static storage frame (SSF) allocated for the program at run time.
What we know from the PowerPC instructions generated for _LSPCO into the user code of a user program?
- The pointer type bytes (higher 8 bytes) of a SLS space pointer and a Teraspace space pointer are hex 8000000000000000 and hex 4000000000000000, respectively.
- The higher 4 bits of a Teraspace address is hex 9. So what does this mean? The higher 3 bytes are the identifier of a Teraspace (let's call it TSID) of an MI process, and the origin of a Teraspace is the TSID plus hex 0000000000 (TSID-0000000000). Through experiments and debugging, you can easily determine that system state Teraspace storage is allocated starting at address TSID-0000000000 and user state Teraspace storage is allocated starting at address TSID-8000000000.
- The origin of an MI space object, the automatic stack of an MI thread, the static storage of an activation group, or a pointer-based heap space is stored in the associated space field (at offset hex 18) of the segment header of the SLS segment allocated for the MI space object, the automatic stack, the static storage, or the pointer-based heap space.
For detailed documentation about the PowerPC instruction set, please refer to the Assembler language reference in the AIX Information Center. The Programming Environments Manual for 64-bit Microprocessors would be a nice reference for the PowerPC architecture. Also note that instructions such as SETTAG (Set Tag) and SELRR are IBM i–specific PowerPC instructions. I've never found public documentation about these instructions. However, they have been discussed by several AS/400 gurus in the MI400 mailing list.
A Real RPG Example
Here is a real RPG example (lspco01.rpgle) that uses the _LSPCO system built-in to retrieve the origins of:
- An MI space object
- The automatic stack of the current thread
- The static storage of the activation group in which the program is activated
- A pointer-based heap space allocated for the activation group in which the program is activated
- The Teraspace of the MI process
h dftactgrp(*no) bnddir('QC2LE')
/if defined(HAVE_I5TOOLKIT) /copy mih-pgmexec /copy ts /else /** * @BIF _MODASA (Modify Automatic Storage Allocation (MODASA)) * * @remark Note that unlike MI instruction MODASA, builtin * _MODASA cannot be used to truncate ASF. Passing a * negative value to _MODASA will raise a Scalar Value * Invalid exception (3203) */ d modasa pr * extproc('_MODASA') d mod_size 10u 0 value * Allocate Teraspace heap storage d ts_malloc pr * extproc('_C_TS_malloc') d size 10i 0 value * Returns a space pointer addressing the System Entry Point Table (SEPT) d sysept pr * extproc('_SYSEPT') /endif * System built-in _LSPCO (Load Space Origin) d lspco pr * extproc('_LSPCO') d spp * value * Display the content of an input MI pointer d dsp_ptr pr d *
d a s d origin@ s * d i_static s
/free // [x] Apply _LSPCO to a space pointer addressing // a space object a@ = sysept(); origin@ = lspco(a@); dsp_ptr(origin@);
// [x] Apply _LSPCO to a space pointer addressing // the SLS automatic stack a@ = modasa(%size(a)); origin@ = lspco(a@); dsp_ptr(origin@);
// [x] Apply _LSPCO to a space pointer addressing // the static storage a@ = %addr(i_static); origin@ = lspco(a@); dsp_ptr(origin@);
// [x] Apply _LSPCO to a space pointer addressing // the SLS heap stroage a@ = %alloc(%size(a@)); origin@ = lspco(a@); dsp_ptr(origin@);
// [x] Apply _LSPCO to a space pointer addressing // the Teraspace heap a@ = ts_malloc(80); origin@ = lspco(a@); dsp_ptr(origin@);
*inlr = *on; /end-free
p dsp_ptr b * cvthc() d cvthc pr extproc('cvthc') d d d 10u 0 value
d dsp_ptr pi d ptr@ *
d ptr_addr@ s * d ds based(ptr_addr@) d hi8 d lo8 d hi16 s d lo16 s
/free ptr_addr@ = %addr(ptr@); cvthc(hi16:hi8:16); cvthc(lo16:lo8:16); dsply hi16 '' lo16; /end-free p e |
Calling program LSPCO01, the output might look like the following:
DSPLY 8000000000000000 333EE74D88001000 DSPLY 8000000000000000 C62EF42483001000 DSPLY 8000000000000000 D7FF DSPLY 8000000000000000 CA062D7689001000 DSPLY 4000000000000000 |
LATEST COMMENTS
MC Press Online