GCC target confusion
Not blogging for two years has left me with quite a lot of things that I'd like to get off my chest. I don't plan on making a habit of negative-outlook posts, but blogging is partially for me to let off steam, and partially for writing things things that other people might find interesting, and today probably leans toward the former.
I've tried submitting a two-character diff to clang, and instructions for reproducing a socket-timeout bug in OpenJDK, both to no avail. I've not personally submitted any bugs to gcc, but one of my friends reported a pretty nasty g++ code-generation bug three years ago, again to no avail. I'm not trying to argue that reporting bugs to open source projects is a waste of time, but I am trying to argue that public shaming is perhaps occasionally a viable alternative approach, and today's unlucky victim is gcc
.
The first thing I'd like to point out is that asking gcc
to create a precompiled header from stdin will result in a segfault:
peter@euphraios:~$ echo | gcc -x c-header - ; echo $?
<stdin>:1:0: internal compiler error: Segmentation fault
Please submit a full bug report,
with preprocessed source if appropriate.
See <file:///usr/share/doc/gcc-4.8/README.Bugs> for instructions.
1
This is somewhat embarrasing, but maybe I'm the only person in the world who wants to create a precompiled header from stdin. Then again, on OSX, where gcc
is an alias for clang
, this works just fine:
[littlegate /Users/corsix]$ echo | gcc -x c-header - ; echo $?
0
The second thing I'd like to point out is the g++ code-generation bug linked above. Using gcc
, the following small program exits with a return code of zero:
peter@euphraios:~$ cat test.cpp
struct Counter {
Counter operator++ () { ++value; return *this; }
int value;
};
int main (int argc, char *[]) {
int a[] = {0}, b[] = {0};
Counter c = {0};
(void)((argc > 1) ? a : b)[++c, 0];
return c.value;
}
peter@euphraios:~$ g++ test.cpp ; ./a.out ; echo $?
0
This is clearly a code-generation bug; ++c
should always happen, therefore ++value
should always happen, therefore c.value
should evaluate to one rather than zero. Again, when on a platform where g++
is an alias for clang
, the same program correctly exits with a return code of one:
[littlegate /Users/corsix]$ g++ test.cpp ; ./a.out ; echo $?
1
I'm really rather surprised that this bug has been sitting around known to the g++ developers for the last three years; I'd have thought that if you're a compiler developer, then code generation bugs are both the most serious and most interesting kind of bugs to fix (at least the V8 developers seem to share this point of view).
The third and final thing I'd like to point out relates to the title of this blog post. Let us begin with a piece of code which tries to call __builtin_ia32_vzeroall
. Obviously, as this function isn't defined, calling it results in a compile-time error:
peter@euphraios:~$ cat test.c
void f() { __builtin_ia32_vzeroall(); }
peter@euphraios:~$ g++ -c test.c ; echo $?
test.c: In function ‘void f()’:
test.c:1:36: error: ‘__builtin_ia32_vzeroall’ was not declared in this scope
void f() { __builtin_ia32_vzeroall(); }
^
1
My previous statement was a slight lie. As you might have guessed, __builtin_ia32_vzeroall
isn't a random name which I pulled out of thin air. In fact, if we pass -mavx
, then it becomes a predeclared function, and we can call it without issue:
peter@euphraios:~$ cat test.c
void f() { __builtin_ia32_vzeroall(); }
peter@euphraios:~$ g++ -mavx -c test.c ; echo $?
0
Maybe we don't want to apply -mavx
to our entire source file, and instead we just want to apply it to certain functions. One way of doing this is via __attribute__((target))
. In the following example, we apply this attribute to f
but not to h
, and as such, the compiler is fine with the call within f
, but raises an error about the call within h
:
peter@euphraios:~$ cat test.c
void f() __attribute__((__target__("avx")));
void f() { __builtin_ia32_vzeroall(); }
void h() { __builtin_ia32_vzeroall(); }
peter@euphraios:~$ g++ -c test.c ; echo $?
test.c: In function ‘void h()’:
test.c:3:37: error: ‘__builtin_ia32_vzeroall’ needs isa option -m32
void h() { __builtin_ia32_vzeroall(); }
^
1
I'm somewhat unsettled about function attributes affecting which symbols are valid, but hey, let's run with it for now.
If __attribute__((target))
isn't your cup of tea, then #pragma GCC target
can be used to the same effect. Again, in the following example, the call within g
is perfectly fine, but the call within h
is not:
peter@euphraios:~$ cat test.c
#pragma GCC push_options
#pragma GCC target("avx")
void g() { __builtin_ia32_vzeroall(); }
#pragma GCC pop_options
void h() { __builtin_ia32_vzeroall(); }
peter@euphraios:~$ g++ -c test.c ; echo $?
test.c: In function ‘void h()’:
test.c:5:37: error: ‘__builtin_ia32_vzeroall’ needs isa option -m32
void h() { __builtin_ia32_vzeroall(); }
^
1
What perplexes me is that if you combine both of these approaches to locally enabling -mavx
, then it seems to become enabled globally. In the following example, __attribute__((target))
is applied to f
, #pragma GCC target
is applied to g
, and then by the time we reach h
, gcc seems to have suffered target confusion and accepts __builtin_ia32_vzeroall
even though it shouldn't be valid:
peter@euphraios:~$ cat test.c
void f() __attribute__((__target__("avx")));
void f() { __builtin_ia32_vzeroall(); }
#pragma GCC push_options
#pragma GCC target("avx")
void g() { __builtin_ia32_vzeroall(); }
#pragma GCC pop_options
void h() { __builtin_ia32_vzeroall(); }
peter@euphraios:~$ g++ -c test.c ; echo $?
0