There are at least two\({}^1\) z/OS C calling conventions, the traditional “LE” OSLINK calling convention, and the newer\({}^2\) XPLINK convention. In the LE calling convention, parameters aren’t passed in registers, but in an array pointed to by R1. Here’s an example of an OSLINK call to strtof():
* float strtof(const char *nptr, char **endptr); LA r0,ep(,r13,408) LA r2,buf(,r13,280) LA r4,#wtemp_1(,r13,416) L r15,=V(STRTOF)(,r3,4) LA r1,#MX_TEMP3(,r13,224) ST r4,#MX_TEMP3(,r13,224) ST r2,#MX_TEMP3(,r13,228) ST r0,#MX_TEMP3(,r13,232) BASR r14,r15 LD f0,#wtemp_1(,r13,416)
R1 is pointed to r13 + 224 (a location on the stack). If the original call was:
float f = strtof( mystring, &err );
The compiler has internally translated it into something of the form:
STRTOF( &f, mystring, &err );
where all of {&f, mystring, &err} are stuffed into the memory starting at the 224(R13) location. Afterwards the value has to be loaded from memory into a floating point register (F0) so that it can be used. Compare this to a Linux strtof() call:
* char * e = 0; * float x = strtof( "1.0", &e ); 400b74: mov $0x400ef8,%edi ; first param is address of "1.0" 400b79: movq $0x0,0x8(%rsp) ; e = 0; 400b82: lea 0x8(%rsp),%rsi ; second param is &e 400b87: callq 400810 <strtof@plt> ; call the function, returning a value in %xmm0
Here the input parameters are RDI, RSI, and the output is XMM0. Nice and simple. Since XPLINK was designed for C code, we expect it to be more sensible. Let’s see what an XPLINK call looks like. Here’s a call to fmodf:
* float r = fmodf( 10.0f, 3.0f ); LD f0,+CONSTANT_AREA(,r9,184) LD f2,+CONSTANT_AREA(,r9,192) L r7,#Save_ADA_Ptr_9(,r4,2052) L r6,=A(__fmodf)(,r7,76) L r5,=A(__fmodf)(,r7,72) BASR r7,r6 NOP 9 LDR f2,f0 STE f2,r(,r4,2144) * * printf( "fmodf: %g\n", (double)r );
There are some curious details that would have to be explored to understand the code above (why f0, f2, and not f0,f1?), however, the short story is that all the input and output values in (floating point) registers.
The mystery that led me to looking at this was a malfunctioning call to strtof:
* float x = strtof( "1.0q", &e ); LA r2,e(,r4,2144) L r7,#Save_ADA_Ptr_12(,r4,2052) L r6,=A(strtof)(,r7,4) L r5,=A(strtof)(,r7,0) LA r1,+CONSTANT_AREA(,r9,20) BASR r7,r6 NOP 17 LR r0,r3 CEFR f2,r0 STE f2,x(,r4,2148) * * printf( "strtof: v: %g\n", x );
The CEFR instruction converts an integer to a (hfp32) floating point representation, so we appear to have strtof returning it’s value in R3, which is an integer register. That then gets copied into R0, and finally into F2 (and after that into a stack spill location before the printf call.) I scratched my head about this code for quite a while, trying to figure out if the compiler had some mysterious way to make this work that I wasn’t figuring out. Eventually, I clued in. I’m so used to using a C++ compiler that I forgot about the old style implicit int return for an unprototyped function. But I had included <stdlib.h> in this code, so strtof should have been prototyped? However, the Language Runtime reference specifies that on z/OS you need an additional define to have strtof visible:
Without the additional define, the call to strtof() is as if it was prototyped as:
My expectation is that with such a correction, the call to strtof() should return it’s value in f0, just like fmodf() does. The result should also not be garbage!
Footnotes:
- There is also a “metal” compiler and probably a different calling convention to go with that. I don’t know how metal differs from XPLINK.
- Newer in the lifetime of the mainframe means circa 2001, which is bleeding edge given the speed that mainframe development moves.