GCC `__attribute__ ((pure))` suggestion on & ldquo; state of the entry & rdquo; getter method - correct?

advertisements

Compiling with -Wsuggest-attribute=pure makes GCC suggest potential functions that can be marked with __attribute__ ((pure)) for optimization purposes.

Here's the definition of pure on GCC's documentation:

Many functions have no effects except the return value and their return value depends only on the parameters and/or global variables. Such a function can be subject to common subexpression elimination and loop optimization just as an arithmetic operator would be. These functions should be declared with the attribute pure.

I'm creating a small game engine, where I have an input_context class that contains an input_state member. The input_context class updates the input_state member every frame by getting the global input state from the operating system.

It also contains several "getters" to query the input state.

Simplified example:

class input_context
{
private:
    input_state _input_state;

public:
    void update()
    {
        os::fill_input_state(_input_state);
    }

    auto mouse_x() const noexcept
    {
        return _input_state._mouse_x;
    }

    auto mouse_y() const noexcept
    {
        return _input_state._mouse_y;
    }

    auto is_key_down(keycode k) const noexcept
    {
        // `_keys` is an array of `bool` values.
        return _input_state._keys[k];
    }
};

GCC is telling me that all these "getter methods", like mouse_x(), mouse_y() and is_key_down(), are candidates for __attribute__ ((pure)).

Should I mark these methods as pure?

I don't think so, but GCC's suggestion makes me wonder about it.

I'm not sure how to interpret GCC's definition of pure - it says that function who rely only on the parameters and/or global variables should be marked as such.

  • In a way, the global OS input state could be interpreted as a global variable.

  • On the other hand, the "getter methods" always return different values, depending on the _input_state member variable.


I believe it's OK to mark it as pure. Considering your example in a simplified form with some added IO functions:

#include <stdio.h>
class X {
  int x_=0;
public:
  int x() const noexcept __attribute__ ((pure)) /*__attribute__((noinline))*/;
  void inc() noxcept { x_++; }
};
int X::x() const noexcept { puts("getting x"); return x_;}
int main(){
  X x;
  printf("%d\n", x.x() + x.x() + x.x());
  x.inc();
  printf("%d\n", x.x() + x.x() + x.x());
}

pure allows you to get:

getting x
0
getting x
3

instead of

getting x
getting x
getting x
0
getting x
getting x
getting x
3

at optimization level at least -O1 (at higher levels you might need to add __attribute__((noinline)) to prevent inlinining).

It's OK if the state changes between two consecutive calls to those getters as long as the compiler can detect the state has changed. If you need to run a non-const method to change the state, then that's no purity violation. If however the state changes on its own (the system changes it/another thread changes it/a signal handler changes it) in a way that the compiler can't know of the change, then the pure attribute is no longer legit.