http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52708
Bug #: 52708
Summary: suboptimal code with __builtin_constant_p
Classification: Unclassified
Product: gcc
Version: 4.7.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c
AssignedTo: [email protected]
ReportedBy: [email protected]
Consider the following code:
--------------------
#include <stdio.h>
int bar(void) __attribute__((__const__));
int main( int argc, char **argv ) {
int res;
res = (__builtin_constant_p(bar()) ? 0 : 1);
printf( "%d\n", res );
res = (__builtin_constant_p(bar()) ? 0 : bar());
printf( "%d\n", res );
return(0);
}
--------------------
It produces the following i386 asm output for main (gcc47 -O3 -S):
(gcc47 (FreeBSD Ports Collection) 4.7.0 20120128 (experimental))
--------------------
main:
pushl %ebp
movl %esp, %ebp
pushl %ebx
andl $-16, %esp
subl $16, %esp
call foo ; foo called before first printf
movl $1, 4(%esp)
movl $.LC0, (%esp)
movl %eax, %ebx ; return value needs to be saved
call printf
movl %ebx, 4(%esp) ; foo should have been called here
movl $.LC0, (%esp)
call printf
xorl %eax, %eax
movl -4(%ebp), %ebx
leave
ret
--------------------
When the __const__ attribute is removed, gcc does produce the right code.
I've worked out a more elaborate example below that shows that gcc can emit
code to evaluate the argument of __builtin_constant_p(). The foo call above
comes from __builtin_constant_p(foo()). I expected __builtin_constant_p() to be
a compile time constant that never produces any code.
In the case below there are two functions foo() and bar(). They are identical
except for a __const__ attribute. Both call a function increment() that simply
counts how many times it's called and then they return their argument. main()
has three tests with __builtin_constant_p().
--------------------
#include <stdio.h>
int count;
void increment(void);
int foo(int a) __attribute__((__const__));
int bar(int a);
void increment(void) {
count++;
}
int foo(int a) {
increment();
return(a);
}
int bar(int a) {
increment();
return(a);
}
int main( int argc, char **argv ) {
int res;
/* without const attribute */
count = 0;
res = (__builtin_constant_p(bar(argc)) ? 0 : bar(argc));
printf( "count(%d) res(%d)\n", count, res );
/* with const attribute (bar) */
count = 0;
res = (__builtin_constant_p(foo(argc)) ? 0 : bar(argc));
printf( "count(%d) res(%d)\n", count, res );
/* with const attribute (foo) */
count = 0;
res = (__builtin_constant_p(foo(argc)) ? 0 : foo(argc));
printf( "count(%d) res(%d)\n", count, res );
return(0);
}
--------------------
Outputs of this program with various optimisation levels:
% gcc47 -O0 -o test test.c
% ./test
count(1) res(1)
count(1) res(1)
count(1) res(1)
% gcc47 -O1 -o test test.c
% ./test
count(1) res(1)
count(2) res(1)
count(0) res(1)
% gcc47 -O2 -o test test.c
% ./test
count(1) res(1)
count(2) res(1)
count(2) res(1)
% gcc47 -O3 -o test test.c
% ./test
count(1) res(1)
count(2) res(1)
count(2) res(1)
Possible outcomes:
1) count(1) res(1): ok: __builtin_constant_p returned false, foo or bar called
once.
2) count(2) res(1): not ok: code was emitted for __builtin_constant_p(foo())
resulting in a call to increment(). __builtin_constant_p returned false, then
foo or bar is called which call increment() a second time.
3) count(0) res(1): not really ok: __builtin_constant_p returned false, foo
called once, but because foo is declared const the call can be eliminated if
the compiler can reuse the result of a previous call, so count stays zero.
However, there's only one call to foo() in the above code.
Note that in this test program foo() isn't really const. If foo is truly const
I could not get gcc to produce wrong code, only suboptimal code as in the first
test program. Sometimes it is useful to let the compiler treat a function as
const even if it has side effects though, such as math functions that can
produce FPU exceptions. The current behaviour of __builtin_constant_p defeats
this (i.e. there could be two exceptions instead of (at most) one).
As a final note, replacing ?: with __builtin_choose_expr results in errors
like:
bug.c:33: error: first argument to '__builtin_choose_expr' not a constant
bug.c:38: error: first argument to '__builtin_choose_expr' not a constant
I expected __builtin_constant_p(expr) to be a compile time constant expression
even if expr isn't.