Inconsistent gcc behaviour for __attribute((const))

c++, compiler-optimization, function-attributes, gcc

Solution

The compiler doesn't trust you. Since you have a reference argument, the compiler doesn't seem to trust your `const` attribute - a `const` function is supposed to only look at values passed through the arguments (not references or dereferencing pointers).

Another way to test this is to break the `const` function out in a separate compilation unit:

test1.cpp:

#include <stdio.h>
int global = 0; // forces results to be computed

int f(int i) __attribute((const));
void print_count(void);

#define TestOp(OP) \
    { \
        int n = 0; \
        global += (f(n) OP f(n)); \
        printf("op %s ", #OP);\
        print_count();\
    }

int main() {
    // all calls optimized away
    TestOp(^)   // 0
    TestOp(-)   // 0
    // one call is optimized away
    TestOp(|)   // 1
    TestOp(&)   // 1
    TestOp(||)  // 1
    TestOp(&&)  // 1
    // no optimization
    TestOp(+)   // 2
    TestOp(*)   // 2

    return global;
}

counter.cpp:

#include <stdio.h>
static int counter = 0;

int f(int i) {
    ++counter;
    return 0;
}

void print_count(void)
{
   printf("counter %d\n", counter);
    counter = 0;
}

Now the compiler figures out that there's no need to call `f(0)` until `f(0) | f(0)`, and the result of that one call to `f(0)` is re-used for the other cases.

$ g++ -O2 -c counter.cpp && g++ -O2 -c test.cpp && g++ counter.o test.o && ./a.out
op ^ counter 0
op - counter 0
op | counter 1
op & counter 0
op || counter 0
op && counter 0
op + counter 0
op * counter 0

Problem

I've come accross a very strange behavior in gcc regarding operators and functions marked with `__attribute((const))`. Logical and arithmetic operators lead to different optimizations, and I don't understand why. It's not really a bug since `__attribute((const))` is only a hint and there's no guarantee to its effect, but still this is very surprising. Anyone has any explanation ? Here's the code. So I define an `__attribute((const))` function: ``` int f(int & counter) __attribute((const)); int f(int & counter) { ++counter; return 0; } ``` Then I define an operator testing macro. This is done with macros and not templates/functors to present simple code to the compiler and simplify the optimization: ``` int global = 0; // forces results to be computed #define TestOp(OP) \ { \ int n = 0; \ global += (f(n) OP f(n)); \ std::cout << "op" #OP " calls f " << n << " times" << std::endl; \ } ``` And finally, I test different operators as follows. The comments match the output with `g++-4.8 -std=c++11 -O2 -Wall -pedantic` same output at `-O3` and `-Ofast` ``` int main() { // all calls optimized away TestOp(^) // 0 TestOp(-) // 0 // one call is optimized away TestOp(|) // 1 TestOp(&) // 1 TestOp(||) // 1 TestOp(&&) // 1 // no optimization TestOp(+) // 2 TestOp(*) // 2 return global; } ``` My question is: why do arithmetic operators yield two calls? Why couldn't `f()+f()` be optimized as `2*f()` ? Is there a way to help/force this optimization ? At first I thought multiplication might be more expensive, but I tried with `f()+....+f()` and 10 additions still don't reduce to `10*f()`. Also, since it's `int` arithmetic, operation order is irrelevant (contrary to `float`s). I also checked the asm but it doesn't help: all ints seem to be pre-computed at compile-time.

Original source