Pure/Const Function Attributes in Different Compilers

pure/const function attributes in different compilers

  • GCC: pure/const function attributes
  • llvm-gcc: supports the GCC pure/const attributes
  • Clang: seems to support it (I tried on a simple example with the GCC style attributes and it worked.)
  • ICC: seems to adopt the GCC attributes (Sorry, only a forum post.)
  • MSVC: Seems not to support it. (discussion)

In general, it seems that almost all compilers support the GCC attributes. MSVC is so far the only compiler which does not support them (and which also doesn't have any alternative).

__attribute__((const)) vs __attribute__((pure)) in GNU C

The difference is explained in the GCC manuals. Most notably a const function may only use the arguments passed in and not any memory, whereas a pure function can access memory too, under constraints:

The pure attribute prohibits a function from modifying the state of the program that is observable by means other than inspecting the function’s return value. However, functions declared with the pure attribute can safely read any non-volatile objects, and modify the value of objects in a way that does not affect their return value or the observable state of the program.

The __attribute__ ((pure)) means that the function has no side effects and the value returned depends on the arguments and the state of global variables. Therefore it is safe for the optimizer to elide some calls to it, if the arguments are the same, and the caller did not do anything to change the state of the globals in between the calls.

The __attribute__ ((const)) means that the return value is solely a function of the arguments, and if any of the arguments are pointers, then the pointers must not be dereferenced.

A const function is always pure.

Examples of const functions would be the abs functions from <stdlib.h> and some mathematical functions from <math.h>: sqrt, exp, etc. (Though they might be subject to rounding modes).

Examples of pure but non-const functions would be such functions as strlen - as it dereferences the pointer passed in.

Pure/const functions in C++

I would expect the output:

I would expect the input:

int bar(int x) {
return foo(x) * 100;
}

Your code actually looks strange for me. As a maintainer I would think that either foo actually has side effects or more likely rewrite it immediately to the above function.

How does it work out in practice? If I mark such functions as pure/const, could it break anything (considering that the code is all correct)?

If the code is all correct then no. But the chances that your code is correct are small. If your code is incorrect then this feature can mask out bugs:

int foo(int x) {
globalmutex.lock();
// complicated calculation code
return -1;
// more complicated calculation
globalmutex.unlock();
return x;
}

Now given the bar from above:

int main() {
cout << bar(-1);
}

This terminates with __attribute__((const)) but deadlocks otherwise.

It also highly depends on the implementation. For example:

void f() {
for(;;)
{
globalmutex.unlock();
cout << foo(42) << '\n';
globalmutex.lock();
}
}

Where the compiler should move the call foo(42)? Is it allowed to optimize this code? Not in general! So unless the loop is really trivial you have no benefits of your feature. But if your loop is trivial you can easily optimize it yourself.

EDIT: as Albert requested a less obvious situation, here it comes:
F
or example if you implement operator << for an ostream, you use the ostream::sentry which locks the stream buffer. Suppose you call pure/const f after you released or before you locked it. Someone uses this operator cout << YourType() and f also uses cout << "debug info". According to you the compiler is free to put the invocation of f into the critical section. Deadlock occurs.

Effects of declaring a function as pure or const to GCC, when it isn't

But what can happen if you attach __attribute__((__pure__))
to a function that doesn't match the above description,
and does have side effects?

Exactly. Here's a short example:

extern __attribute__((pure)) int mypure(const char *p);

int call_pure() {
int x = mypure("Hello");
int y = mypure("Hello");
return x + y;
}

My version of GCC (4.8.4) is clever enough to remove second call to mypure (result is 2*mypure()). Now imagine if mypure were printf - the side effect of printing string "Hello" would be lost.

Note that if I replace call_pure with

char s[];

int call_pure() {
int x = mypure("Hello");
s[0] = 1;
int y = mypure("Hello");
return x + y;
}

both calls will be emitted (because assignment to s[0] may change output value of mypure).

Is it simply the possibility that the function will be called fewer times
than you would want it to be, or is it possible to create
undefined behaviour or other kinds of serious problems?

Well, it can cause UB indirectly. E.g. here

extern __attribute__((pure)) int get_index();

char a[];
int i;
void foo() {
i = get_index(); // Returns -1
a[get_index()]; // Returns 0
}

Compiler will most likely drop second call to get_index() and use the first returned value -1 which will result in buffer overflow (well, technically underflow).

But what can actually happen if you attach __attribute__((__const__))
to a function that does access global memory?

Let's again take the above example with

int call_pure() {
int x = mypure("Hello");
s[0] = 1;
int y = mypure("Hello");
return x + y;
}

If mypure were annotated with __attribute__((const)), compiler would again drop the second call and optimize return to 2*mypure(...). If mypure actually reads s, this will result in wrong result being produced.

EDIT

I know you asked to avoid hand-waving but here's some generic explanation. By default function call blocks a lot of optimizations inside compiler as it has to be treated as a black box which may have arbitrary side effects (modify any global variable, etc.). Annotating function with const or pure instead allows compiler to treat it more like expression which allows for more aggressive optimization.

Examples are really too numerous to give. The one which I gave above is common subexpression elimination but we could as well easily demonstrate benefits for loop invariants, dead code elimination, alias analysis, etc.

Value/Purpose of __attribute((const)) in gcc c++

__attribute__((const)) in GNU C expresses the intent of the author of the function to not depend on any value other than its input arguments.

This allows the compiler to optimize multiple calls with identical arguments to such a function into a single call without having to analyze the function body. This is especially useful if the function's body is in another translation unit.

In the case of int Add( int x , int y ) __attribute__((const)), multiple calls to, say Add(2,3), could be coalesced into a single call and the return value could be cached, without knowing what Add actually does.

It also allows the compiler to verify that the function actually adheres to the declared intent.

Refer to this LWN article for more details and an example.

pure/const functions in C++0x

  1. Has this changed in C++0x?

No. There is a constexpr but it means compile time constant. If its parameters are constexprs too then it's executed at compile time, but it's a regular function otherwise. Since they must be defined in the same translation unit and consist of a single return statement they probably will be inlined and the above optimization will be performed. It can't be used to provide the compiler information about externally linked function.

If it has not changed, why not? I think it might be quite useful to have such a support.

Actually I don't think you need it. The language is already too big, and the programmer can easily rewrite this code to be more efficient based on her knowledge anyway. Unlike restrict it doesn't provide any info that can't be expressed by other means.

Are there any open proposals about it?

I haven't seen any committee papers on that topic.

GNU const/pure attributes vs constexpr

When applied to functions, is there a difference between the const attribute and the constexpr specifier?

There are differences.

Firstly, C does not have constexpr, so you cannot take advantage of it in that language.

Calls to constexpr function can be constant expressions. As such, their result can be used for example as the size of an array. The GNU attributes cannot be used to achieve the same (ignoring GCC VLA language extension).

Constexpr functions are good for taking advantage of pre-calculation at compile time. The GNU attributes are still useful for allowing the compiler take advantage of runtime constness. For example, let's say there is a function that cannot be constexpr - perhaps because it calls a non-constexpr function. But we may know that every call to the function produces same output with no side-effects. Const attribute allows the compiler to not repeat redundant calls.

Another difference is that constexpr functions must be defined inline. Non-constexpr functions don't need to be defined inline.

const gcc function attribute and a global const variables

These attributes allow the compiler to know whether it's safe to omit calls to the function without knowing how it's implemented.

pure attribute basically says that the function result only depends on the function arguments and the global state; additionally, the function itself does not mutate global state.

If you call a pure function twice, it's guaranteed to return the same result; however, if you mutate globally visible state between the calls, the guarantee no longer holds.

const attribute is stronger in that even if the global state is mutated, the function still should return the same result; thus it's safe to optimize the redundant calls to const function in more cases.

Reading global state should not be a problem if you can guarantee that the state does not change (note that marking the global as const does not always guarantee that).

As an example, consider this program:

int foo(int) __attribute__((pure));
int bar(int) __attribute__((const));
void unknown();

int test1(int a)
{
int x = foo(a);
int y = foo(a);
return x + y;
}

int test2(int a)
{
int x = bar(a);
int y = bar(a);
return x + y;
}

int test3(int a)
{
int x = foo(a);
unknown();
int y = foo(a);
return x + y;
}

int test4(int a)
{
int x = bar(a);
unknown();
int y = bar(a);
return x + y;
}

Compiling it with gcc 4.8.1 and analysing the assembly reveals that test1() and test2() both only call the respective function once and then multiply the result by 2; test3() does 3 calls - 2 calls to foo and 1 call to unknown; test4() does a call to bar() followed by a call to unknown(), and returns the result of bar(), multiplied by 2.

This behavior matches the explanation above - unknown() can mutate global state, so compiler can't elide extra calls to foo(), but can elide extra calls to bar().



Related Topics



Leave a reply



Submit