Continuing my series of pointless complaining about compiler behavior (see here and here for the previous entries), I recently downloaded a trial version of PGI’s compiler to put in my Linux virtual machine to see how that does compiling qthreads. There were a few minor things that it choked on that I could correct pretty easily, and some real bizarre behavior that seems completely broken to me.
Subtle Bugs in My Code
Let’s start with the minor mistakes it found in my code that other compilers hadn’t complained about:
static inline uint64_t qthread_incr64(
volatile uint64_t *operand, const int incr)
{
union {
uint64_t i;
struct {
uint32_t l, h;
} s;
} oldval, newval;
register char test;
do {
oldval.i = *operand;
newval.i = oldval.i + incr;
__asm__ __volatile__ ("lock; cmpxchg8b %1\n\t setne %0"
:"=r"(test)
:"m"(*operand),
"a"(oldval.s.l),
"d"(oldval.s.h),
"b"(newval.s.l),
"c"(newval.s.h)
:"memory");
} while (test);
return oldval.i;
}
Seems fairly straightforward, right? Works fine on most compilers, but the PGI compiler complains that “%sli” is an invalid register. Really obvious error, right? Right? (I don’t really know what the %sli register is for either). Turns out that because setne
requires a byte-sized register, I need to tell the compiler that I want a register that can be byte-sized. In other words, that "=r"
needs to become "=q"
. Fair enough. It’s a confusing error, and thus annoying, but I am technically wrong (or at least I’m providing an incomplete description of my requirements) here so I concede the ground to PGI.
Unnecessary Pedantry
And then there are places where PGI is simply a bit more pedantic than it really needs to be. For example, it generates an error when you implicitly cast a volatile struct foo *
into a void *
when calling into a function. Okay, yes, the pointers are different, but… most compilers allow you to implicitly convert just about any pointer type into a void *
without kvetching, because you aren’t allowed to dereference a void pointer (unless you cast again, and if you’re casting, all bets are off anyway), thus it’s a safe bet that you want to work on the pointer rather than what it points to. Yes, technically PGI has made a valid observation, but I disagree that their observation rises to the level of “warning-worthy” (I have no argument if they demote it to the sort of thing that shows up with the -Minform=inform
flag).
Flat-out Broken
But there are other places where PGI is simply wrong/broken. For example, if I have (and use) a #define
like this:
#define PARALLEL_FUNC(initials, type, shorttype, category) \
type qt_##shorttype##_##category (type *array, size_t length, int checkfeb) \
{ \
struct qt##initials arg = { array, checkfeb }; \
type ret; \
qt_realfunc(0, length, sizeof(type), &ret, \
qt##initials##_worker, \
&arg, qt##initials##_acc, 0); \
return ret; \
}
PARALLEL_FUNC(uis, aligned_t, uint, sum);
PGI will die! Specifically, it complains that struct qtuisarg
does not exist, and that an identifier is missing. In other words, it blows away the whitespace following initials so that this line:
struct qt##initials arg = { array, checkfeb }; \
is interpreted as if it looked like this:
struct qt##initials##arg = { array, checkfeb }; \
But at least that’s easy to work around: rename the struct so that it has a _s
at the end! Apparently PGI is okay with this:
struct qt##initials##_s arg = { array, checkfeb }; \
::sigh:: Stupid, stupid compiler. At least it can be worked around.
Thwarting The Debugger
PGI also bad at handling static inline functions in headers. How bad? Well, first of all, the DWARF2 symbols it generates (the default) are incorrect. It gets the line-numbers right but the file name wrong. For example, if I have an inline function in qthread_atomics.h
on line 75, and include that header in qt_mpool.c
, and then use that function on line 302, the DWARF2 symbols generated will claim that the function is on line 75 of qt_mpool.c
(which isn’t even correct if we assume that it’s generating DWARF2 symbols based on the pre-processed source! and besides which, all the other line numbers are from non-pre-processed source). You CAN tell it to generate DWARF1 or DWARF3 symbols, but then it simply leaves out the line numbers and file names completely. Handy, no?
Everyone Else is Doing It…
Here’s another bug in PGI… though I suppose it’s my fault for outsmarting myself. So, once upon a time, I (think I) found that some compilers require assembly memory references to be within parentheses, while others require them to be within brackets. Unfortunately I didn’t write down which ones did what, so I don’t remember if I was merely being over-cautious in my code, or if it really was a compatibility problem. Nevertheless, I frequently do things like this:
atomic_incr(volatile uint32_t *op, const int incr) {
uint32_t retval = incr;
__asm__ __volatile__ ("lock; xaddl %0, %1"
:"=r"(retval)
:"m"(*op), "0"(retval)
:"memory");
return retval;
}
Note that weird "m"(*op)
construction? That was my way of ensuring that the right memory reference syntax was automatically used, no matter what the compiler thought it was. So, what does PGI do in this instance? It actually performs the dereference! In other words, it behaves as if I had written:
atomic_incr(volatile uint32_t *op, const int incr) {
uint32_t retval = incr;
__asm__ __volatile__ ("lock; xaddl %0, (%1)"
:"=r"(retval)
:"r"(*op), "0"(retval)
:"memory");
return retval;
}
when what I really wanted was:
atomic_incr(volatile uint32_t *op, const int incr) {
uint32_t retval = incr;
__asm__ __volatile__ ("lock; xaddl %0, (%1)"
:"=r"(retval)
:"r"(op), "0"(retval)
:"memory");
return retval;
}
See the difference? <sigh> Again, it’s not hard to fix so that PGI does the right thing. And maybe I was being too clever in the first place. But dagnabit, my trick should work! And, more pointedly, it DOES work on other compilers (gcc and icc at the bare minimum, and I’ve tested similar things with xlc).