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.
As your T.A., I have to punish you …
December 19, 2020 C/C++ development and debugging. grading comments, horrible code, macros, token pasting
Back in university, I had to implement a reverse polish notation calculator in a software engineering class. Overall the assignment was pretty stupid, and I entertained myself by generating writing a very compact implementation. It worked perfectly, but I got a 25/40 (62.5%) grade on it. That mark was well deserved, although I did not think so at the time.
The grading remarks were actually some of best feedback that I ever received, and also really funny to boot. I don’t know the name of this old now-nameless TA anymore, but I took his advice to heart, and kept his grading remarks on my wall in my IBM office for years. That served as an excellent reminder not to write over complicated code.
Today, I found those remarks again, and am posting them for posterity. Enjoy!
Transcription for easy reading
Reflection.
The only part of this feedback that I would refute was the comment about the string class. That was a actually a pretty good string implementation. I didn’t write it because I was a viscous mouse hunter, but because I hit a porting issue with pre-std:: C. In particular, we had two sets of Solaris machines available to us, and I was using one that had a compiler that included a nice C++ string class. So, naturally I used it. For submission, our code had to compile an run on a different Solaris machine, and lo and behold, the string class that all my code was based on was not available.
What I should have done (20/20 hindsight), was throw out my horrendous code, and start over from scratch. However, I took the more fun approach, and wrote my own string class so that my machine would compile on either machine.
Amusingly, when I worked on IBM LUW, there was a part of the query optimizer code seemed to have learned all it’s tricks from the ugly macros and token pasting that I did in this assignment. It was truly gross, but there was 10000x more of it than my assignment. Having been thoroughly punished for my atrocities, I easily recognized this code for the evil it was. The only way that you could debug that optimizer code, was by running it through the preprocessor, cut and pasting the results, and filtering that cut and paste through something like cindent (these days you would probably use clang-format.) That code was brutal, and I always wished that it’s authors had had the good luck of having a TA like mine. That code is probably still part of LUW terrorizing developers. Apparently the justification for it was that it was originally written by an IBM researcher using templates, but templates couldn’t be used in DB2 code because we didn’t have compiler on all platforms that supported them at the time.
I have used token pasting macros very judiciously and sparingly in the 26 years since I originally used them in this assignment, and I do think that there are a few good uses for that sort of generative code. However, if you do have to write that sort of code, I think it’s better to write perl (or some other language) code that generates understandable code that can be debugged, instead of relying on token pasting.
Share this:
Like this: