The C compiler on z/OS has an extension which allows you to put assembler code inline within a C program. This function can be useful, for example accessing z/OS macros. __asm__ is very badly documented in the publications, but this post gives a good overview.
Overall the use of __asm__ works, but you have to be careful. For small bits of assembler it was quicker to use __asm__ instead of creating a small assembler program and linking that with the C program.
This blog post document some of my experiences.
- Using and compiling the code.
- Basic function.
- Long statements – wrapping and continuation.
- What are the __asm__ parameters?
- Using C variables in the assembler code.
- Using constants is not quite what I expected.
- Using generated registers – or not.
- Some instructions do not work.
- Using literals
Using and compiling the code
You put code in __asm__(…); , _asm(..); or asm(..); . I think these are all the same.
To use macros or copy files within the code you need the ASMLIB statement in your JCL.
//S1 JCLLIB ORDER=CBC.SCCNPRC //STEP1 EXEC PROC=EDCC,INFILE='COLIN.C.SOURCE(ASM)', // LNGPRFX='CBC',LIBPRFX='CEE', // CPARM='OPTFILE(DD:SYSOPTF)' //COMPILE.ASMLIB DD DSN=SYS1.MACLIB,DISP=SHR
Basic function
The asm() instruction has the following parts
- asm(
- “a string of source which can contain %[symbolname] “. Each line of assembler has “\n” at the end of the line.
- the output code will be formatted to conform to normal HLASM layout standards.
- “:” a list of output symbols and their mapping to C variables.
- “:” a list of symbols used as input and their mapping to C variable names.
- “:” a list of register that may have been changed (clobbered) by this code, for example “r14” and “r15”.
- “);”
Example code
__asm__(
“*2345678901234567890xxxxxxxxxxxxxxxxxxxxxxxx\n”
” WTO ‘%[PARMS]’ \n”
:
:
[PARMS]”s”(“zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz”)
: “r0”, “r1”, “r14”, “r15”
);
The PARMS statement is a string with a value ZZZZZ… It is used in the WTO ‘%[PARMS]\n’ statement.
Long statements – wrapping and continuation
The generated code from the above statement is
*2345678901234567890xxxxxxxxxxxxxxxxxxxxxxxx 000023 WTO 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzX 000023 zzz' 000023
We can see
- the *234… starts in column 1
- the WTO instruction is in column 10
- because the string was very long, it has been split at column 71 and wrapped onto the next line at column 16.
- A continuation character was inserted at column 72
This means you do not need to worry too much about the formatting of the data.
The code looks a bit buggy.
Making the WTO into an operand and a comment
__asm__(
“*2345678901234567890xxxxxxxxxxxxxxxxxxxxxxxx\n”
” WTO abc ‘%[PARMS]’ \n”
:
:
[PARMS]”s”(“zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz”)
: “r0”, “r1”, “r14”, “r15”
);
Gives a warning message
*2345678901234567890xxxxxxxxxxxxxxxxxxxxxxxx WTO abc 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzX zzzzzzzzzzzzzzzzzzzzzzzzzzz' ASMA432W Continuation statement may be in error - comma omitted from continued statement.
What are the __asm__ parameters?
The first parameter is a C string containing the assembler instructions. Each line ends with a “\n”. You specify substitution variables using %[name] where the name is defined later.
You can use multiple lines, for example
asm(
” LA R2,%[p1] \n”
” TIMEUSED LINKAGE=SYSTEM,CPU=MIC,STORADR=(2) \n”
:
The C compiler treats the “…” “…” as one long literal constant.
There are three sets of values between the delimiters “:” “:” “:”
- Output variables
- Input variables
- General Registers which your code changes
A C variable can be used for
- Output only. This is in the output section. This has a definition with “=”. For example [p1] “=m”(pASCB). asm() may generate code to load load the value before use
- Input and output. This is in the output section. This has a definition with “+”. For example [i1] “+m”(data), asm() may generate code to load the value before use, and store it afterwards.
- Input only. This is in the input section. It does not have character with its definition. For example [i2] “m”(data)
- Dummy – but used as a register. If you specify you want a register (let asm() which register), it needs a variable either to load, or to store depending if it is write or read or both. For example [rr] “=r”(val). I defined the C variable “val”.
Using C variables in the assembler code
There is a variety of different “types” of data, from memory, to offset. I could not see the difference between them. Some gave the same output. I tended just to use “m” for memory fields.
Use of variables
// this code gets the ASCB address from low core
long pASCB;
asm(” LLGT 1,548 \n”
” STG 1,%[p1] \n”
: [p1] “=m”(pASCB)
:
: “r1”
);
This (64 bit) code
- Clears and loads register 1 with the value in address decimal 548. (The ASCB value) .
- It stores register 1 in the the variable %[p1]
- [p1] is defined as
- “=” means this field is write only
- m is a memory address
- (pASCB) is the variable to use. The compiler replaces this with (in my case) the value 2248(4) – the address of the variable in format offset(base regiser).
- There was no input data
- Register r1 was “clobbered” (meaning it was changed in my assembler code).
Using constants is not quite what I expected.
printf(“ttime %ld\n”,data);
asm(” LA 1,%[i1] ccp\n”
” LA 1,%[i2] \n”
” LA 1,%[i3]\n”
:
: [i1] “i”(“999”),
[i2] “i”(998)
[i3] “i”(“=c\’ABCD\'”)
: “r1″,”r2”
);
Gives code
LA 1,999
LA 2,998
LA 2,=c'ABCD'
Using [i2] “i”(“COLIN”) gave
LA 2,COLIN
ASMA044E Undefined symbol – COLIN
An example of using registers
David Clayford posted the following
inline bool isauth() { int rc; __asm (" TESTAUTH FCTN=1" : "=NR:r15"(rc) : : "r1", "r14", "r15"); return rc == 0;
This code invokes the TESTAUTH macro which gives a return code in register 15.
The “=NR: r15” (rc) means
- = the __ASM__ only writes, it does not read the variable
- NR : Use the named register
- r15 use this register
- (rc) and store it in the variable called rc
Using generated registers – or not
You specify that you want a register allocated to you by using the type “r”.
int val = 40;
asm(” LLGT %[rr],548 pASCB \n”
” STG %[rr],%[p1] ZZZZZ \n”
: [p1] “=m”(pASCB)
: [rr] “r”(val)
: “r1″,”r2”
);
The lack of a “=”, “+” or “&” in front of the “r” means read only use of the register, so load the register with the value before my code.
Produces
LGF r6,val(,r4,2248) - This is generated
LLGT 6,548 pASCB
STG 6,2240(4) ZZZZZ
This code has been given register 6 to use
- It loaded the value of val into it – because I had specified it in the list of input variables value.
- Used the same register where-ever I had specified %[rr] .
When I had specified the register as an input/output register by
: [p1] “=m”(pASCB), [rr] “+r”(val)
:
: “r1″,”r2”
The “+” says it is read and written the output code was
LGF r6,val(,r4,2248) Generated
LLGT 6,548 My Code pASCB
STG 6,2240(4) My Code ZZZZZ
LGR r0,r6 Generated
LGFR r0,r0 Generated
ST r0,val(,r4,2248) Generated
So there is code generated to load the register from val, and save the value of the last of my instruction in the variable val.
Personally, I do not think I would use the “r”, but would select my own register(s) and use them.
If I wanted to used C variables, I can specify those, and explicitly load and save them.
Some instructions do not work.
char buffer[256];
asm(
” MVC [%p1](4),548 pASCB \n”
: [p1] “=m”(buffer)
:
: “r1″,”r2”
);
This fails with
: [p1] “=m”(buffer)
CCN4454 Operand must be an lvalue.
You need to use
You need to use [p1] “=m”(buffer[0]) instead of (buffer). (But this is just normal C)
The instruction then fails because
MVC 2240(4)(4),548
Is not a valid instruction.
You need to use
char buffer[256];
asm(
” LA 1,%[p1] \n”
” MVC 0(4,1),548 pASCB \n”
:
[p1] “=m”(buffer[0])
:
: “r1″,”r2”
);
Which successfully generates
LA r1,2240(r4,) MVC 0(4,r1),548
Using literals
You can use assembler literals in your code, for example
asm(
” LA 1,=C’ABCD’ \n”
:
:
: “r1”
);
This works. There is a section in the listing
Start of ASM Literals =C'ABCD' End of ASM Literals
Using assembler macros
When you use a macro, you need to review the generated code, and make a note of the registers it uses, then update the “clobbers” list.
asm(
” LA 2,%[p1] \n”
” TIMEUSED LINKAGE=SYSTEM,CPU=MIC,STORADR=(2) \n”:…
This used r14,r0,r15
There was an error
BNZ *+8
*** ASMA307E No active USING for operand *+8
I had to use the following to get it to work.
long long CPUUSED;
asm(
” PUSH USING \n”
” BASR 3,0 \n”
” USING *,3 \n”
” LA 2,%[p1] \n”
” TIMEUSED LINKAGE=SYSTEM,CPU=MIC,STORADR=(2) \n”
” POP USING \n”
:
[p1] “=m”(CPUUSED)
:
: “r0″,”r1″,”r2”,“r3”,r14″,”r15″
);
printf(“TIMEUSED %ld\n”,CPUUSED);