Discussion:
GCC Common-Function-Attributes/const
cmdLP #CODE
2018-11-26 20:30:31 UTC
Permalink
Dear GCC-developer team,

The specification of the const-attribute is a bit ambiguous, it does not
fully specify which global variables are allowed to be read from. Obviously
constant global compile-time initialized variables can be accessed without
problem. But what about run time initialized variables like a lookup table
or the initialization of an api? Does GCC may invoke the function before
the initialization of the lookup table/api; or is it guaranteed, that the
function is only called in places, where it would be called without the
attribute. I also propose some additional attributes to let the programmer
clarify his/her intentions.

Here are some code snippets, how GCC might change the generated code,
according to the documentation.
In my opinion, each generated output should be assigned to a distinct
attribute.

*Function declarations:*
*void init_values();*
*int read_value(int index) [[gnu::const]];*

*void use_value(int value);*

*Problematic code:*
*int main(int argc, char** argv) {*
* init_values();*
*for(int i=1; i < argc; ++i) {*
* int value = read_value(0); // constant*
* use_value(value, argv[i]);*
* }*
*}*

Here are some possible outputs, which are possible, because of the
ambiguity of the documentation. The attribute next to "Transformation" is
the proposed new attribute names, which could lead to the transformatio
(when [[gnu::const]] is replaced with it).
*Transformation [[gnu::const, gnu::is_simple]]*
*int read_value__with_0;*

*int main(int argc, char**) {*
* read_value__with_0 = read_value(0);*
* init_values();*
*for(int i=1; i < argc; ++i) {*
* use_value(read_value__with_0, argv[i]);*
* }*
*}*

The code is called at some undefined point, maybe at the start of main,
even if it is never used, because the compiler assumes, that the function
simply reads from constant memory/does a simple calculation. In the case of
the example, it is not what the programmer intended.

*Transformation [[gnu::const]]*
*int read_value__with_0;*

*int main(int argc, char** argv) {*
* if(argc > 1) read_value__with_0 = read_value(0);*
* init_values();*
* for(int i=1; i < argc; ++i) {*
* use_value(read_value__with_0, argv[i]);*
* }*
*}*

This is almost the same to the previous version, but it only calls the
function, if the result is used.

Both attributes (the time when it is called is undefined) can also result
in the following code where the attributes give a stronger guarantee, when
it is called (the first time). The following works semantically as the
programmer intended/when the attributes were ignored.

*Transformation** [[gnu::const(init_values()), gnu::is_simple]]*
*int read_value__with_0;*
*int main() {*
* init_values();*
* read_value__with_0 = read_value(0);*
*for(int i=1; i < 100; ++i) {*
* use_value(read_value__with_0, argv[i]);*
* }*
*}*

*Transformation **[[gnu::const(init_values())]]*
*int read_value__with_0;*
*int main() {*
* init_values();*
* if(argc > 1) read_value__with_0 = read_value(0);*
*for(int i=1; i < 100; ++i) {*
* use_value(read_value__with_0, argv[i]);*
* }*
*}*


The function is guaranteed to never use the returned values again, when the
given expression in the attribute parameter list is called, so the compiler
interprets the given function as an initializing function.


*PROPOSED ATTRIBUTES*

*[[gnu::is_simple]]*
In connection to the [[gnu::const]] attribute this attribute implies, that
the function might be called when the result is not used. Eg. when the
function is called in a conditional branch, the call can be extracted from
the branch and can be called outside. This should be applied to very simple
functions.

*[[gnu::const]]*
The meaning is kept as before. But the function can be called any time.
This enshures, that no expression is called inside the function, which has
an observable effect. But when the function is used in a
[[gnu::const(...)]] annotated function each call to this function is
considered an observable effect to the function.


*[[gnu::const([(<parameter list>):]<expression>,...)]]*
This is an extension to the old [[gnu::const]] attribute. But: When some of
the expressions is called, the previous return values are now invalid (the
function depends on the expression). The parameter list specifies variables
used in the expression, which are not known. In the expression, the
parameter of the annotated function itself, member variables/functions and
static variables/functions can be used. If [[gnu::const]] is used on non
static member functions, the function automaticly depends on the
constructor.
Destructors automaticly depend on all non static member functions.

Unclear: A function should never depend on a function/expression, which
calls the annotated function.


*[[gnu::calls([(<parameter list>):]<expression>,...)]]*
This function attributes tells the compiler, that the given expression
might be called inside. This makes older values recieved from
[[gnu::const(...)]] functions (which depend on (parts of) the expression)
dirty when called. The attribute is inherited to parent functions, which
call the annotated function (only if the implementation is known, the
compiler should suggest this for the functions). The functions called in
the expression itself do not need to be implemented/linked. The parameter
list specifies variables used in the expression, which are not known. In
the expression, the parameter of the annotated functions, member
variables/functions and global variables/functions can be used.

The compiler should suggest this attribute, when it sees an expression
inside the body, which is referenced by [[gnu::const(...)]].



*Benefits*
*class map {*
* //...*
*public:*
* void set(int key, int value) [[gnu::const]];*
* void get(int key) [[gnu::const((int value): this->set(key, value))]];*
*};*

The function *get* is annotated with the proposed new extension of the
*const* attribute, the attribute specifies, that the return value is
constant, until the *set* member function is called with the same *key*
value.

The *set* function does not change any state, which can be observed by any
functions. Except *get* (or the destructor), which directly depends on this
function. This also implies, that all calls except the last to this
function can be omitted, before *get* is called. The function only depends
on the constructor (automaticly).

class counter {
//...
public:
void increase() [[gnu::const(this->increase(), this->reset())]];
void reset() [[gnu::const]]
int get() [[gnu::const(this->increase(), this->reset())]];
};

When referencing to itself with the [[gnu::const(...)] attribute, repeating
calls cannot be omitted.


It is important, that sideeffects violating the gurantee given by the
attributes should not cause undefined behavior (when debugging), the
compiled programm should simply invoke none or the whole sideeffects; this
is important for logging/debugging, which sould be possible inside such
functions. Maybe it can be undefined for release builds.

(In release mode) This would be undefined behavior, but a valid
optimization, if for example *glob* is freed in *foo* and

*int foo() [[const]];*
*//...*
*char* ptr = glob;*
*int y = foo();*
*ptr = glob;*
*puts(ptr);*

is transformed to

*char* ptr = glob;*
*int y = foo();*
*puts(ptr);*

In debugmode, changes like the change of *glob* should be allowed in foo,
so that output functions work. Maybe restrict the changes only on output
functions.




On questions, you can write to me back.

Sincerely,
cmdLP
Martin Sebor
2018-11-26 23:37:33 UTC
Permalink
Post by cmdLP #CODE
Dear GCC-developer team,
The specification of the const-attribute is a bit ambiguous, it does not
fully specify which global variables are allowed to be read from. Obviously
constant global compile-time initialized variables can be accessed without
problem. But what about run time initialized variables like a lookup table
or the initialization of an api? Does GCC may invoke the function before
the initialization of the lookup table/api; or is it guaranteed, that the
function is only called in places, where it would be called without the
attribute. I also propose some additional attributes to let the programmer
clarify his/her intentions.
The purpose of the const attribute is to let GCC assume that
invocations of a function it's on with the same argument values
yield the same result no matter when they are made. Any function
that satisfies this constraint can be declared with the attribute,
whether it depends on values of global objects or not, const or
not, just as long as their values do not change between any two
invocations of the function in a way that would affect its result.

It should even be possible to define a const function to return
the value of non-constant dynamically initialized data. For
instance:

__attribute__ ((const)) int prime (int i)
{
static int primes[N];
if (!primes[0])
// fill primes with prime numbers
return primes[i];
}

This is a valid const function because its result depends only
on the value of its argument.

The documentation for the attribute is being improved as we speak
in response to PR79738. I expect it to take a few more tweaks
before we're completely happy with it.
Post by cmdLP #CODE
Here are some code snippets, how GCC might change the generated code,
according to the documentation.
In my opinion, each generated output should be assigned to a distinct
attribute.
*Function declarations:*
*void init_values();*
*int read_value(int index) [[gnu::const]];*
*void use_value(int value);*
*Problematic code:*
*int main(int argc, char** argv) {*
* init_values();*
*for(int i=1; i < argc; ++i) {*
* int value = read_value(0); // constant*
* use_value(value, argv[i]);*
* }*
*}*
Here are some possible outputs, which are possible, because of the
ambiguity of the documentation. The attribute next to "Transformation" is
the proposed new attribute names, which could lead to the transformatio
(when [[gnu::const]] is replaced with it).
*Transformation [[gnu::const, gnu::is_simple]]*
*int read_value__with_0;*
*int main(int argc, char**) {*
* read_value__with_0 = read_value(0);*
* init_values();*
*for(int i=1; i < argc; ++i) {*
* use_value(read_value__with_0, argv[i]);*
* }*
*}*
The code is called at some undefined point, maybe at the start of main,
even if it is never used, because the compiler assumes, that the function
simply reads from constant memory/does a simple calculation. In the case of
the example, it is not what the programmer intended.
Right. This definition makes read_value() unsuitable for attribute
const.

I don't have a sense of how much software would benefit from
the attributes proposed below.

Martin
Post by cmdLP #CODE
*Transformation [[gnu::const]]*
*int read_value__with_0;*
*int main(int argc, char** argv) {*
* if(argc > 1) read_value__with_0 = read_value(0);*
* init_values();*
* for(int i=1; i < argc; ++i) {*
* use_value(read_value__with_0, argv[i]);*
* }*
*}*
This is almost the same to the previous version, but it only calls the
function, if the result is used.
Both attributes (the time when it is called is undefined) can also result
in the following code where the attributes give a stronger guarantee, when
it is called (the first time). The following works semantically as the
programmer intended/when the attributes were ignored.
*Transformation** [[gnu::const(init_values()), gnu::is_simple]]*
*int read_value__with_0;*
*int main() {*
* init_values();*
* read_value__with_0 = read_value(0);*
*for(int i=1; i < 100; ++i) {*
* use_value(read_value__with_0, argv[i]);*
* }*
*}*
*Transformation **[[gnu::const(init_values())]]*
*int read_value__with_0;*
*int main() {*
* init_values();*
* if(argc > 1) read_value__with_0 = read_value(0);*
*for(int i=1; i < 100; ++i) {*
* use_value(read_value__with_0, argv[i]);*
* }*
*}*
The function is guaranteed to never use the returned values again, when the
given expression in the attribute parameter list is called, so the compiler
interprets the given function as an initializing function.
*PROPOSED ATTRIBUTES*
*[[gnu::is_simple]]*
In connection to the [[gnu::const]] attribute this attribute implies, that
the function might be called when the result is not used. Eg. when the
function is called in a conditional branch, the call can be extracted from
the branch and can be called outside. This should be applied to very simple
functions.
*[[gnu::const]]*
The meaning is kept as before. But the function can be called any time.
This enshures, that no expression is called inside the function, which has
an observable effect. But when the function is used in a
[[gnu::const(...)]] annotated function each call to this function is
considered an observable effect to the function.
*[[gnu::const([(<parameter list>):]<expression>,...)]]*
This is an extension to the old [[gnu::const]] attribute. But: When some of
the expressions is called, the previous return values are now invalid (the
function depends on the expression). The parameter list specifies variables
used in the expression, which are not known. In the expression, the
parameter of the annotated function itself, member variables/functions and
static variables/functions can be used. If [[gnu::const]] is used on non
static member functions, the function automaticly depends on the
constructor.
Destructors automaticly depend on all non static member functions.
Unclear: A function should never depend on a function/expression, which
calls the annotated function.
*[[gnu::calls([(<parameter list>):]<expression>,...)]]*
This function attributes tells the compiler, that the given expression
might be called inside. This makes older values recieved from
[[gnu::const(...)]] functions (which depend on (parts of) the expression)
dirty when called. The attribute is inherited to parent functions, which
call the annotated function (only if the implementation is known, the
compiler should suggest this for the functions). The functions called in
the expression itself do not need to be implemented/linked. The parameter
list specifies variables used in the expression, which are not known. In
the expression, the parameter of the annotated functions, member
variables/functions and global variables/functions can be used.
The compiler should suggest this attribute, when it sees an expression
inside the body, which is referenced by [[gnu::const(...)]].
*Benefits*
*class map {*
* //...*
*public:*
* void set(int key, int value) [[gnu::const]];*
* void get(int key) [[gnu::const((int value): this->set(key, value))]];*
*};*
The function *get* is annotated with the proposed new extension of the
*const* attribute, the attribute specifies, that the return value is
constant, until the *set* member function is called with the same *key*
value.
The *set* function does not change any state, which can be observed by any
functions. Except *get* (or the destructor), which directly depends on this
function. This also implies, that all calls except the last to this
function can be omitted, before *get* is called. The function only depends
on the constructor (automaticly).
class counter {
//...
void increase() [[gnu::const(this->increase(), this->reset())]];
void reset() [[gnu::const]]
int get() [[gnu::const(this->increase(), this->reset())]];
};
When referencing to itself with the [[gnu::const(...)] attribute, repeating
calls cannot be omitted.
It is important, that sideeffects violating the gurantee given by the
attributes should not cause undefined behavior (when debugging), the
compiled programm should simply invoke none or the whole sideeffects; this
is important for logging/debugging, which sould be possible inside such
functions. Maybe it can be undefined for release builds.
(In release mode) This would be undefined behavior, but a valid
optimization, if for example *glob* is freed in *foo* and
*int foo() [[const]];*
*//...*
*char* ptr = glob;*
*int y = foo();*
*ptr = glob;*
*puts(ptr);*
is transformed to
*char* ptr = glob;*
*int y = foo();*
*puts(ptr);*
In debugmode, changes like the change of *glob* should be allowed in foo,
so that output functions work. Maybe restrict the changes only on output
functions.
On questions, you can write to me back.
Sincerely,
cmdLP
Martin Sebor
2018-11-27 16:26:13 UTC
Permalink
[CC gcc list and Sandra]

Thanks for the suggestions. I agree that the documentation
should make it possible to answer at least the basic questions
on your list. We'll see about incorporating them.

In general, attributes that affect optimization are implemented
at a level where higher-level distinctions like those between
pointers and references, or between classes with user-defined
ctors vs PODs, are immaterial. It's probably worth making that
clear in some preamble rather than for each individual attribute.

As far as requests for new attributes or features of existing
attributes I would suggest to raise those individually as
enhancements in Bugzilla where they can be appropriately
prioritized.

Martin

For context:
https://gcc.gnu.org/ml/gcc/2018-11/msg00138.html
Thank you for the reply.
*My suggestions for the documentation*
The documentation should inform if you can annotate c++ member functions
with these attributes (pure, const). (It would make sence to interpret
the whole object referenced by *this as a parameter)
The documentation should clarify how it handles structs/classes/unions
and references. Does it threat references like pointers? Does it only
allow PODs/trivial types to be returned, or does it invoke the copy
constructor, when it is used again? (Eg.(assume PODs/trivial types are
no problem) std::variant or std::optional with trivial types shouldn't
be a problem, but std::variant and std::optional are not trivial).
There should be a way to express, that a returnvalue of a function never
changes until another function is called. In my first e-mail I defined a
class map, which has a getter and setter method; it is obvious, that
calling get with the same key again yields the same value. It should be
optimized to just one call to get. But the value might change, if you
call set with the same key. The old returnvalue cannot be used anymore.
After that  all calls to get can be merged again. This could improve the
performance of libraries using associative arrays.
cmdLP
Richard Biener
2018-11-28 13:41:20 UTC
Permalink
Post by Martin Sebor
Post by cmdLP #CODE
Dear GCC-developer team,
The specification of the const-attribute is a bit ambiguous, it does not
fully specify which global variables are allowed to be read from. Obviously
constant global compile-time initialized variables can be accessed without
problem. But what about run time initialized variables like a lookup table
or the initialization of an api? Does GCC may invoke the function before
the initialization of the lookup table/api; or is it guaranteed, that the
function is only called in places, where it would be called without the
attribute. I also propose some additional attributes to let the programmer
clarify his/her intentions.
The purpose of the const attribute is to let GCC assume that
invocations of a function it's on with the same argument values
yield the same result no matter when they are made. Any function
that satisfies this constraint can be declared with the attribute,
whether it depends on values of global objects or not, const or
not, just as long as their values do not change between any two
invocations of the function in a way that would affect its result.
It should even be possible to define a const function to return
the value of non-constant dynamically initialized data. For
__attribute__ ((const)) int prime (int i)
{
static int primes[N];
if (!primes[0])
// fill primes with prime numbers
return primes[i];
}
This is a valid const function because its result depends only
on the value of its argument.
The documentation for the attribute is being improved as we speak
in response to PR79738. I expect it to take a few more tweaks
before we're completely happy with it.
Note that the compiler, when you annotate a function with const, may
happily re-order a call to such function with the runtime initialization
sequence. So in this context it's only safe to use the const attribution
in case the compiler cannot possibly fall into this "trap". C++
constructors executed as part of global object initialization is probably
safe.
Post by Martin Sebor
Post by cmdLP #CODE
Here are some code snippets, how GCC might change the generated code,
according to the documentation.
In my opinion, each generated output should be assigned to a distinct
attribute.
*Function declarations:*
*void init_values();*
*int read_value(int index) [[gnu::const]];*
*void use_value(int value);*
*Problematic code:*
*int main(int argc, char** argv) {*
* init_values();*
*for(int i=1; i < argc; ++i) {*
* int value = read_value(0); // constant*
* use_value(value, argv[i]);*
* }*
*}*
Here are some possible outputs, which are possible, because of the
ambiguity of the documentation. The attribute next to "Transformation" is
the proposed new attribute names, which could lead to the transformatio
(when [[gnu::const]] is replaced with it).
*Transformation [[gnu::const, gnu::is_simple]]*
*int read_value__with_0;*
*int main(int argc, char**) {*
* read_value__with_0 = read_value(0);*
* init_values();*
*for(int i=1; i < argc; ++i) {*
* use_value(read_value__with_0, argv[i]);*
* }*
*}*
The code is called at some undefined point, maybe at the start of main,
even if it is never used, because the compiler assumes, that the function
simply reads from constant memory/does a simple calculation. In the case of
the example, it is not what the programmer intended.
Right. This definition makes read_value() unsuitable for attribute
const.
I don't have a sense of how much software would benefit from
the attributes proposed below.
Martin
Post by cmdLP #CODE
*Transformation [[gnu::const]]*
*int read_value__with_0;*
*int main(int argc, char** argv) {*
* if(argc > 1) read_value__with_0 = read_value(0);*
* init_values();*
* for(int i=1; i < argc; ++i) {*
* use_value(read_value__with_0, argv[i]);*
* }*
*}*
This is almost the same to the previous version, but it only calls the
function, if the result is used.
Both attributes (the time when it is called is undefined) can also result
in the following code where the attributes give a stronger guarantee, when
it is called (the first time). The following works semantically as the
programmer intended/when the attributes were ignored.
*Transformation** [[gnu::const(init_values()), gnu::is_simple]]*
*int read_value__with_0;*
*int main() {*
* init_values();*
* read_value__with_0 = read_value(0);*
*for(int i=1; i < 100; ++i) {*
* use_value(read_value__with_0, argv[i]);*
* }*
*}*
*Transformation **[[gnu::const(init_values())]]*
*int read_value__with_0;*
*int main() {*
* init_values();*
* if(argc > 1) read_value__with_0 = read_value(0);*
*for(int i=1; i < 100; ++i) {*
* use_value(read_value__with_0, argv[i]);*
* }*
*}*
The function is guaranteed to never use the returned values again, when the
given expression in the attribute parameter list is called, so the compiler
interprets the given function as an initializing function.
*PROPOSED ATTRIBUTES*
*[[gnu::is_simple]]*
In connection to the [[gnu::const]] attribute this attribute implies, that
the function might be called when the result is not used. Eg. when the
function is called in a conditional branch, the call can be extracted from
the branch and can be called outside. This should be applied to very simple
functions.
*[[gnu::const]]*
The meaning is kept as before. But the function can be called any time.
This enshures, that no expression is called inside the function, which has
an observable effect. But when the function is used in a
[[gnu::const(...)]] annotated function each call to this function is
considered an observable effect to the function.
*[[gnu::const([(<parameter list>):]<expression>,...)]]*
This is an extension to the old [[gnu::const]] attribute. But: When some of
the expressions is called, the previous return values are now invalid (the
function depends on the expression). The parameter list specifies variables
used in the expression, which are not known. In the expression, the
parameter of the annotated function itself, member variables/functions and
static variables/functions can be used. If [[gnu::const]] is used on non
static member functions, the function automaticly depends on the
constructor.
Destructors automaticly depend on all non static member functions.
Unclear: A function should never depend on a function/expression, which
calls the annotated function.
*[[gnu::calls([(<parameter list>):]<expression>,...)]]*
This function attributes tells the compiler, that the given expression
might be called inside. This makes older values recieved from
[[gnu::const(...)]] functions (which depend on (parts of) the expression)
dirty when called. The attribute is inherited to parent functions, which
call the annotated function (only if the implementation is known, the
compiler should suggest this for the functions). The functions called in
the expression itself do not need to be implemented/linked. The parameter
list specifies variables used in the expression, which are not known. In
the expression, the parameter of the annotated functions, member
variables/functions and global variables/functions can be used.
The compiler should suggest this attribute, when it sees an expression
inside the body, which is referenced by [[gnu::const(...)]].
*Benefits*
*class map {*
* //...*
*public:*
* void set(int key, int value) [[gnu::const]];*
* void get(int key) [[gnu::const((int value): this->set(key, value))]];*
*};*
The function *get* is annotated with the proposed new extension of the
*const* attribute, the attribute specifies, that the return value is
constant, until the *set* member function is called with the same *key*
value.
The *set* function does not change any state, which can be observed by any
functions. Except *get* (or the destructor), which directly depends on this
function. This also implies, that all calls except the last to this
function can be omitted, before *get* is called. The function only depends
on the constructor (automaticly).
class counter {
//...
void increase() [[gnu::const(this->increase(), this->reset())]];
void reset() [[gnu::const]]
int get() [[gnu::const(this->increase(), this->reset())]];
};
When referencing to itself with the [[gnu::const(...)] attribute, repeating
calls cannot be omitted.
It is important, that sideeffects violating the gurantee given by the
attributes should not cause undefined behavior (when debugging), the
compiled programm should simply invoke none or the whole sideeffects; this
is important for logging/debugging, which sould be possible inside such
functions. Maybe it can be undefined for release builds.
(In release mode) This would be undefined behavior, but a valid
optimization, if for example *glob* is freed in *foo* and
*int foo() [[const]];*
*//...*
*char* ptr = glob;*
*int y = foo();*
*ptr = glob;*
*puts(ptr);*
is transformed to
*char* ptr = glob;*
*int y = foo();*
*puts(ptr);*
In debugmode, changes like the change of *glob* should be allowed in foo,
so that output functions work. Maybe restrict the changes only on output
functions.
On questions, you can write to me back.
Sincerely,
cmdLP
Loading...