gcc bug
The attached program demonstrates a gcc code generation bug. I'm seeing this bug with the November snapshot with all patches applied. Specifically, if I compile the program with "-O", it works fine and I get:
$ gcc -O t.c $ ./a.out feed0000beef
However, if I compile it with "-O2", I get:
$ gcc -O2 t.c $ ./a.out beef
which obviously can't be right.
--david
struct f {
unsigned long lo : 32;
unsigned long hi : 32;
};
unsigned long r;
void
doit (unsigned long value1, unsigned long value2)
{
unsigned long w, t;
((struct f*)&w)->lo = ((struct f*)&value1)->lo;
((struct f*)&w)->hi = ((struct f*)&value2)->hi;
r = w;
}
int
main (int argc, char **argv)
{
doit(0xbeef, 0xfeed00000000);
printf ("%lx\n", r);
}
Quoted 6 lines · David Mosberger
The attached program demonstrates a gcc code generation bug.
struct f {
unsigned long lo : 32;
unsigned long hi : 32;
};
K&R says "Fields may be declared only as ints". The C99 rational says "Three types of bit fields are now defined: plain int calls for implementation-defined signedness (as in K&R), signed int calls for assuredly signed fields, and unsigned int calls for unsigned fields". I am surprised that the compiler accepted unsigned long, it is not listed as a gcc extension. Do you get the same error when those fields are changed to unsigned int?
There was a comment about non-int bitfields. This isn't allowed by the ISO C standard, but it is a very common extension. Most C compilers support it. Gcc will give a warning about this construct if you use -pedantic. This isn't related to the problem though.
The problem with the code is that it uses invalid casts. ISO C says that if you access an object using a type other than what the object was declared with, then the result is undefined. There are a few exceptions, signedness doesn't matter, qualifiers don't matter, aggregate types containing the type are OK, and char is OK. (In the 1989 ANSI C standard, this is in section 3.3 Expressions.) This rule exists so that C compilers can do type-based alias analysis. Gcc tries to do aliasing based on addresses first, but if it gets confused by address arithmetic, then it will try type-based alias analysis. So in this case, it decides that the read of the unsigned int w and the store to struct f w can't alias because the types are different, and then it emits an instruction schedule that causes the code to perform differently than what you expected. This code would probably work on other targets. The lack of addressing modes in IA-64 makes it harder for the alias analysis pass to figure out whether two addresses are the same, and hence we fall back on type-based aliasing more often than we would on say an IA-32 machine.
Gcc does support type punning via unions, though the ISO C standard technically does not allow this. If you write the code like this it should work union g {
struct f f;
unsigned long l;
};
((union g*)&w)->f.lo = ((union g*)&value1)->f.lo; ((union g*)&w)->f.hi = ((union g*)&value2)->f.hi;
However, this is a bit cryptic. It would be clearer, and you will get much more efficient code, if you write it something like this. Using & usually forces values onto the stack. If we do it this way, we do everything in registers until the write to r.
union g {
struct f f;
unsigned long l;
};
doit (unsigned long value1, unsigned long value2)
{
union g w, t, u;
t.l = value1;
w.f.lo = t.f.lo;
u.l = value2;
w.f.hi = u.f.hi;
r = w.l;
}
int
main (int argc, char **argv)
{
doit(0xbeef, 0xfeed00000000);
printf ("%lx\n", r);
}
Jim
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line · Jim Wilson
Jim> There was a comment about non-int bitfields. This isn't Jim> allowed by the ISO C standard, but it is a very common Jim> extension. Most C compilers support it. Gcc will give a Jim> warning about this construct if you use -pedantic. This isn't Jim> related to the problem though.
Yes.
Jim> The problem with the code is that it uses invalid casts.
Oh, you are right.
Jim> Gcc does support type punning via unions, though the ISO C Jim> standard technically does not allow this.
Good.
Jim>If you write the code like this it should work Jim>union g {
Jim> struct f f;
Jim> unsigned long l;
Jim>};
Jim> ((union g*)&w)->f.lo = ((union g*)&value1)->f.lo; Jim> ((union g*)&w)->f.hi = ((union g*)&value2)->f.hi;
OK, that seems to take care of the problem.
Jim> However, this is a bit cryptic. It would be clearer, and you Jim> will get much more efficient code, if you write it something Jim> like this. Using & usually forces values onto the stack. If Jim> we do it this way, we do everything in registers until the Jim> write to r.
Yes, that would be much cleaner and better for performance. Unfortunately, it's not my code and it's a large and complex program, so I'll probably have to make do with the "union" wrapper for now.
Thanks a lot,
--david