Putting assembler code inside a C program

I was using a RACF service, and the documentation casually says “the application must free the returned storage”.

C does not provide facilities to use the STORAGE RELEASE z/OS service, so I had to write some assembler code to do this. It wasn’t difficult, I just fell over many little problems.

The IBM extension to the C language ASM is defined in the documentation.

I think of asm() as a special macro.

  • You pass in strings of assembler code. asm() will format it, and split the line if it would be longer than 72 characters.
  • If you want multiple lines you should end each line with \n (standard printf in C).
  • Lines starting with “*” are treated as comments, and produce no code
  • You can start your line with just one blank, before the instruction and it will be formatted as properly aligned assembler code, wrapping onto new lines if needed.
  • You do not use the C variable names in the assembler source.
    • You specified a mapping like [length] “m”(lDeletestorage), it takes the C variable lDeletestorage, determines its storage address such as it is offset 40 from register 5 (40(5)) and assigns this value to “length”
    • In the assembler code you specify ” L 2,%[length] \n” and it substitutes the value for %[length]. This instruction becomes ” L 2,40(5) “
    • At first glance it you would think it should be able to substitute the value directly, but you need to specify meta information about the field (is it used read only or read/write, is it a storage location, or a literal string). This is why there is this indirection
  • You need to specify which registers are modified so the C code can either save/restore their values, or just use different registers.

My code

if ( pDeletestorage != 0 )
{
int freerc = 0;
#define ASM_PREFIX " SYSSTATE ARCHLVL=2 \n"
asm(
ASM_PREFIX
" L 2,%[length] \n"
" L 4,%[a] \n"
" STORAGE RELEASE,LENGTH=(2),ADDR=(4),COND=YES,RTCD=%[rc] "
: [rc] "+m"(freerc) //* output
: [length] "m"(lDeletestorage,
[a] "m"(pDeletestorage)
:"r0", "r1" , "r2" , "r4" , "r15" );
printf("Storage release rc %i\n",rc);
}
  • if ( pDeletestorage != 0 ) If there is a block of storage to release
  • {
  • int freerc = 0; define and preset the return code
  • #define ASM_PREFIX ” SYSSTATE ARCHLVL=2 \n” see below. The \n makes a new line
  • asm( generate the code
  • ASM_PREFIX insert the SYSTATE code
  • ” L 2,%[length] \n” Load register 2 with the value of the variable length defined below
  • ” L 4,%[a] \n” Load register 4 with the value of the variable a(ddress) defined below
  • ” STORAGE RELEASE,LENGTH=(2),ADDR=(4),COND=YES,RTCD=%[rc] “ issue the storage macro
  • : After the first : is a list of output variables
    • [rc] “+m”(freerc) //* output [rc] matches the name in the RTCD=%[rc]
      • “+” means Indicates that the operand is both read and written by the instruction
      • “m” is use a memory object – that is, a variable. Other options are literal values or registers.
      • (freerc) is the name of the C variable
  • : After the second : is a list of input variables
    • [length] “m”(lDeletestorage),
    • [a] “m”(pDeletestorage)
  • : After the third : is a list of register that are changed (clobbered)
    • “r0”, “r1” , “r2” , “r4” , “r15”
  • );
  • printf(“Storage release rc %i”,rc);
  • }

You have to pass the data to the macro using registers. You can either select registers yourself, or let the compiler pick them for you. See Using registers with my program as a more correct way of coding the program.

Note: If you pick the registers yourself, they may be unavailable if you compile it as a 64 bit program or as XPLINK, so using Using registers with my program is better.g

With the SYSSTATE ARCHLVL=2

This statement sets the minimum architecture to  z/Architecture (which includes Jump instructions).

The generated code was

* SYSSTATE ARCHLVL=2                                              
* THE VALUE OF SYSSTATE IS NOW SET TO ASCENV=P AMODE64=NO ARCHLVX01-SYSS
* L=2 OSREL=00000000 RMODE64=NO
L 2,1368(13)
L 4,1364(13)

STORAGE RELEASE,LENGTH=(2),ADDR=(4),COND=YES,RTCD=1372(13)
LR 0,2 .STORAGE LENGTH
LR 1,4 .ADDRESS OF STORAGE
LHI 15,X'0001' .Add in parameters
L 14,16(0,0) .CVT ADDRESS
L 14,772(14,0) .ADDR SYST LINKAGE TABLE
L 14,204(14,0) .OBTAIN LX/EX FOR RELEASE
PC 0(14) .PC TO STORAGE RTN
ST 15,1372(13) .SAVE RETURN CODE

Where

  L     2,1368(13)       
L 4,1364(13)

is loading the registers with the variable data from the C code,

and

  ST     15,1372(13)                  .SAVE RETURN CODE      

saves the return code into the C variable.

Without the SYSSTATE ARCHLVL=2

The code failed to compile because of addressability issues.

         L     2,1368(13)                                              
L 4,1364(13)
STORAGE RELEASE,LENGTH=(2),ADDR=(4),COND=YES,RTCD=1372(13)
CNOP 0,4
B IHB0001B .BRANCH AROUND DATA
*** ASMA307E No active USING for operand IHB0001B
IHB0001F DC BL1'00000000'
DC AL1(0*16) .KEY
DC AL1(0) .SUBPOOL
DC BL1'00000001' .FLAGS
IHB0001B DS 0F
LR 0,2 .STORAGE LENGTH
LR 1,4 .ADDRESS OF STORAGE
L 15,IHB0001F .CONTROL INFORMATION
*** ASMA307E No active USING for operand IHB0001F
L 14,16(0,0) .CVT ADDRESS
L 14,772(14,0) .ADDR SYST LINKAGE TABLE
L 14,204(14,0) .OBTAIN LX/EX FOR RELEASE
PC 0(14) .PC TO STORAGE RTN
ST 15,1372(13) .SAVE RETURN CODE

In days of old you had base registers to locate data and instructions. Data reference was relative to (USING) a base register. Branching within a program used one or more base register. A re-entrant program would get dynamic storage, and this would be addressed by its own base register.

Modern instructions include the Jump instruction. This says jump this many half words to the instruction. These instructions do not need a base register.

With SYSSTATE ARCHLVL=2, the generated code for the C part of the program used the jump instructions, and did not need a base register. Assembler macros that generate code the old way, need a base register.

Using old instruction, to load a value into a register, it was loaded from storage – which needed a base register to locate the storage. For example

IHB0001F DC     BL1'00000000'                                          
DC AL1(0*16) .KEY
DC AL1(0) .SUBPOOL
DC BL1'00000001' .FLAGS
...
L 15,IHB0001F .CONTROL INFORMATION

Modern instructions have the “constant” value as part of the instruction

 LHI    15,X'0001'             

This effectively clears register 15 and loads the value 0x0001 into the bottom. (To be accurate it moves 0x0001 to the bottom half, then propagates the sign bit to the upper half word. The sign bit is 0.)

Using registers

The z/OS uses registers 14,15,0,1 for linkage, and these are likely to be used when using macros. Register 3 was used by the C code to locate storage, so I used registers 2 and 4.

Using variables

Reading the documentation, it seems I should be able to say

 " STORAGE RELEASE,LENGTH=%[length],ADDR=(4),COND=YES,RTCD=%[rc] "
: ...
: [length] "m"(lDeletestorage),
[a] "m"(pDeletestorage)
:...

passing in a variable. This does not work, because C does not pass in “lDeletestorage”, but the offset and register.

For example the storage for variable lDeleteme is 1368 off register 13.

It can be used in a Load instruction

  L     2,1368(13)  

But not every where. In the macro it get substituted.

STORAGE RELEASE,LENGTH=1368(13),ADDR=(4),COND=YES,RTCD=1372(13)
L 0,=A(1368(13)) .STORAGE LENGTH
*** ASMA035S Invalid delimiter - 1368(13)

Passing back the return code using RTCD=%[rc]. The macro checks to see if the value of RTCD is a register, if not then use the value in a store instruction.

Using registers

You can specify that you want to use a register, and the ASM() will pick registers for you.

 int res = 0x12345678;        
int newRes = 55;
__asm(" SR %[rx],%[rx] clear \n"
" SR %[rx],%[ry] them \n"
" LR %[rx],%[ry] COLINS\n"

: [rx]"=r"(res) // output
: [ry]"r"(newRes) // input
);

This generates code

     L        r4,newRes(,r13,1380) 
* SR 2,2 clear
* SR 2,4 them
* LR 2,4 COLINS
SR r2,r2
SR r2,r4
LR r2,r4

LR r0,r2
ST r0,res(,r13,1376)

It has selected to use registers 2 and 4.

Where

  • L r4,newRes(,r13,1380) loads the input value into a register of its choice
  • *… these are comments of the coded instructions
  • SR.. LR.. are the generated instructions
  • LR r0,r2 , ST r0,res(,r13,1376) saves the variable defined as output back into C storage.
Using registers with my program

The compiler decides which registers to user ( r2 and r4). When I compiled this code as 64 bit (using option lp64) it used registers 6 and 7.

asm( ASM_PREFIX 
" STORAGE RELEASE,LENGTH=(%[length]),ADDR=(%[a]),COND=YES,RTCD=%[rc] "
: [rc] "+m"(freerc) //* output
: [length] "r"(lDeleteme),
[a] "r"(pDeleteme)
:"r0", "r1" , "r15" );

This generates

 *          asm( 
L r2,lDeleteme(,r13,1368)
L r4,pDeleteme(,r13,1364)
SYSSTATE ARCHLVL=2

STORAGE RELEASE,LENGTH=(2),ADDR=(4),COND=YES,RTCD=1372(13)
LR 0,2 .STORAGE LENGTH
LR 1,4 .ADDRESS OF STORAGE
LHI 15,X'0001' .Add in parameters @PCA
L 14,16(0,0) .CVT ADDRESS
L 14,772(14,0) .ADDR SYST LINKAGE TABLE
L 14,204(14,0) .OBTAIN LX/EX FOR RELEASE
PC 0(14) .PC TO STORAGE RTN
ST 15,1372(13) .SAVE RETURN CODE

Using the different data types.

This is not explained very well in the documentation.

Using literal integer constants

The code

asm( "LABEL LA     4,%[i] \n" 
:
: [i] "i"(99)
: "r4" );

Generates

 * LABEL    LA    4,99                    Colins             
LA r4,99

The label I specified on the instruction is not added to the created instruction.

Using literal string constants

Instead of using this capability, you could use C constants, and the “m” definition.

The code

asm( "LABEL LA     4,%[i] Colins \n" 
:
: [i] "i"("ABCD")
: "r4" );

Gives a compile error

10 LABEL    LA    4,ABCD                  Colins    
*** ASMA044E Undefined symbol - ABCD

The code

asm("LABEL LA     4,%[i] Colins \n" 
:
: [i] "i"("=C'ABCD'")
: "r4" );

Generates

* LABEL    LA    4,=C'ABCD'              Colins          
LA r4,0(,r3)
...
Start of ASM Literals
000360 C1C2C3C4 =C'ABCD'

Using a memory operand that is offsetable (o)

The code

asm("LABEL LA     4,%[i] Colins \n" 
"LABEL2 LA 4,%[j] Colins2\n"
:
: [i] "o"(res),
[j] "m"(res)
: "r4" );

produced

* LABEL    LA    4,1376(13)              Colins      
* LABEL2 LA 4,1376(13) Colins2
LA r4,1376(r13,)
LA r4,1376(r13,)

So it looks like the “o” operand is the same as specifying “m”.

64 bit programming

When you compile in 64 bit code (option lp64). The asm() works just as well It is better to use Using registers with my program so you do not have to guess which registers you can use.

My code

asm( ASM_PREFIX 
" STORAGE RELEASE,LENGTH=(%[l]),ADDR=(%[a]),COND=YES,RTCD=%[rc] "
: [rc] "+m"(freerc) //* output
: [l] "r"(lDeleteme),
[a] "r"(pDeleteme)
:"r0", "r1" , "r15"
);

Generated

    LLGF     r6,lDeleteme(,r4,3504) 
LG r7,pDeleteme(,r4,3496)
SYSSTATE ARCHLVL=2
...

Where the LLGF loads the 32 bit length variable into register 6, and LG loads the 64 bit address variable into register 7.

Compiling the code

I used the shell script

name=irrseq
export _C89_CCMODE=1
p1="-Wc,arch(8),target(zOSV2R3),list,source,ilp32,gonum,asm,float(ieee)"
p5=" -I. "


p7="-Wc,ASM,ASMLIB(//'SYS1.MACLIB') -Wa,LIST,RENT"

p8="-Wc,LIST(c.lst),SOURCE,NOWARN64,XREF,SHOWINC "

xlc $p1 $p5 $p7 $p8 -c $name.c -o $name.o

l1="-Wl,LIST,MAP,XREF "
/bin/xlc $name.o -o irrseq -V $l1 1>a

The important line, p7, contains

  • -Wc,ASM,ASMLIB(//’SYS1.MACLIB’) C compile options
    • ASM Enables inlined assembly code inside C/C++ programs.
    • ASMLIB Specifies assembler macro libraries to be used when assembling the assembler source code.
  • -Wa,LIST,RENT” Assembler options
    • LIST Instructs the assembler to produce a listing
    • RENT Specifies that the assembler checks for possible coding violations of program reenterability

That’s strange – the compile worked.

I was setting up a script to compile some C code in Unix Services, and it worked – when I expected the bind to fail because I had not specified where to find a stub file.

How to compile the source

I used a shell script to compile and bind the source. I was surprised to see that it worked, because it needed some Linkedit stubs from CSSLIB. I thought I needed

export _C89_LSYSLIB=”CEE.SCEELKEX:CEE.SCEELKED:CBC.SCCNOBJ:SYS1.CSSLIB”

but it worked without it.

The script

name=irrseq 

export _C89_CCMODE=1
# export _C89_LSYSLIB="CEE.SCEELKEX:CEE.SCEELKED:CBC.SCCNOBJ:SYS1.CSSLIB"
p1="-Wc,arch(8),target(zOSV2R3),list,source,ilp32,gonum,asm,float(ieee)"
p5=" -I. "
p8="-Wc,LIST(c.lst),SOURCE,NOWARN64,XREF,SHOWINC "

xlc $p1 $p5 $p8 -c $name.c -o $name.o
# now bind it
l1="-Wl,LIST,MAP,XREF "
/bin/xlc $name.o -o irrseq -V $l1 1>binder.out

The binder output had

XL_CONFIG=/bin/../usr/lpp/cbclib/xlc/etc/xlc.cfg:xlc 
-v -Wl,LIST,MAP,XREF irrseq.o -o./irrseq
STEPLIB=NONE
_C89_ACCEPTABLE_RC=4
_C89_PVERSION=0x42040000
_C89_PSYSIX=
_C89_PSYSLIB=CEE.SCEEOBJ:CEE.SCEECPP
_C89_LSYSLIB=CEE.SCEELKEX:CEE.SCEELKED:CBC.SCCNOBJ:SYS1.CSSLIB

Where did these come from? – I was interested in SYS1.CSSLIB. It came from xlc config file below.

xlc config file

By default the compile command uses a configuration file /usr/lpp/cbclib/xlc/etc/xlc.cfg .

The key parts of this file are

* FUNCTION: z/OS V2.4 XL C/C++ Compiler Configuration file
*
* Licensed Materials - Property of IBM
* 5650-ZOS Copyright IBM Corp. 2004, 2018.
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*

* C compiler, extended mode
xlc: use = DEFLT
...* common definitions
DEFLT: cppcomp = /usr/lpp/cbclib/xlc/exe/ccndrvr
ccomp = /usr/lpp/cbclib/xlc/exe/ccndrvr
ipacomp = /usr/lpp/cbclib/xlc/exe/ccndrvr
ipa = /bin/c89
as = /bin/c89
ld_c = /bin/c89
ld_cpp = /bin/cxx
xlC = /usr/lpp/cbclib/xlc/bin/xlc
xlCcopt = -D_XOPEN_SOURCE
sysobj = cee.sceeobj:cee.sceecpp
syslib = cee.sceelkex:cee.sceelked:cbc.sccnobj:sys1.csslib
syslib_x = cee.sceebnd2:cbc.sccnobj:sys1.csslib
exportlist_c = NONE
exportlist_cpp = cee.sceelib(c128n):cbc.sclbsid(iostream,complex)
exportlist_c_x = cee.sceelib(celhs003,celhs001)
exportlist_cpp_x = ...
exportlist_c_64 = cee.sceelib(celqs003)
exportlist_cpp_64 = ...
steplib = NONE

Where the _x entries are for xplink.

It is easy to find the answer when you know the solution.

Note:

Without the export _C89_CCMODE=1

I got

IEW2763S DE07 FILE ASSOCIATED WITH DDNAME /0000002 CANNOT BE OPENED
BECAUSE THE FILE DOES NOT EXIST OR CANNOT BE CREATED.
IEW2302E 1031 THE DATA SET SPECIFIED BY DDNAME /0000002 COULD NOT BE
FOUND, AND THUS HAS NOT BEEN INCLUDED.
FSUM3065 The LINKEDIT step ended with return code 8.

Compiling in 64 bit

It was simple to change the script to compile it in 64 bit mode, but overall this didn’t work.

p1="-Wc,arch(8),target(zOSV2R3),list,source,lp64,gonum,asm,float(ieee)" 
...
l1="-Wl,LIST,MAP,XREF -q64 "

When I compiled in 64 bit mode, and tried to bind in 31/32 bit mode (omitting the -q64 option) I got messages like

 IEW2469E 9907 THE ATTRIBUTES OF A REFERENCE TO isprint FROM SECTION irrseq#C DO
NOT MATCH THE ATTRIBUTES OF THE TARGET SYMBOL. REASON 2
...
IEW2469E 9907 THE ATTRIBUTES OF A REFERENCE TO IRRSEQ00 FROM SECTION irrseq#C
DO NOT MATCH THE ATTRIBUTES OF THE TARGET SYMBOL. REASON 3

IEW2456E 9207 SYMBOL CELQSG03 UNRESOLVED. MEMBER COULD NOT BE INCLUDED FROM
THE DESIGNATED CALL LIBRARY.
...
IEW2470E 9511 ORDERED SECTION CEESTART NOT FOUND IN MODULE.
IEW2648E 5111 ENTRY CEESTART IS NOT A CSECT OR AN EXTERNAL NAME IN THE MODULE.

IEW2469E THE ATTRIBUTES OF A REFERENCE TO … FROM SECTION … DO
NOT MATCH THE ATTRIBUTES OF THE TARGET SYMBOL. REASON x

  • Reason 2 The xplink attributes of the reference and target do not match.
  • Reason 3 Either the reference or the target is in amode 64 and the amodes do not match. The IRRSEQ00 stub is only available in 31 bit mode, my program was 64 bit amode.

IEW2456E SYMBOL CELQINPL UNRESOLVED. MEMBER COULD NOT BE INCLUDED
FROM THE DESIGNATED CALL LIBRARY.

  • The compile in 64 bit mode generates an “include…” of the 64 bit stuff needed by C. Because the binder was in 31 bit, it used the 31 bit libraries – which did not have the specified include file. When you compile in 64 bit mode you need to bind with the 64 bit libraries. The compile command sorts all this out depending on the options.
  • The libraries used when binding in 64 bit mode are syslib_x = cee.sceebnd2:cbc.sccnobj:sys1.csslib. See the xlc config file above.

IEW2470E 9511 ORDERED SECTION CEESTART NOT FOUND IN MODULE.
IEW2648E 5111 ENTRY CEESTART IS NOT A CSECT OR AN EXTERNAL NAME IN THE MODULE.

  • Compiling in 64 bit mode, generates an entry point of CELQSTRT instead of CEESTART, so the binder instructions for 31 bit programs specifying the entry point of CEESTART will fail.

Overall

Because IRRSEQ00 only comes in a 31 bit flavour, and not a 64 bit flavour, I could not call it directly from a 64 bit program, and I had to use a 32 bit compile and bind.