Search code examples
clanguage-lawyercompiler-optimizationrestrict-qualifier

Does a restrict-qualified pointer parameter of a function allow optimization of the caller functions?


A restrict-qualified pointer parameter of a function is an effective way to allow compilers to optimize that function:

int f(const int *restrict p) {
  int n=*p;
  printf("Debug\n");
  return *p==n;
}

Here Clang 17 with -O3 will just return 1 without re-examining *p, and without performing the comparison. Without restrict, *p has to be re-examined and the comparison has to be performed. This is because printf is an external function, and the compiler doesn't know if it might modify *p. Indeed, if p happens to be pointing into the file position indicator of stdout, printf is actually going to modify it.

So, as a general principle, if you want maximum optimization for the function, and your function ever calls an external function, it is not meaningless to declare all your parameters restrict. Of course, this places a severe restriction on callers, because it requires them to promise that they will not call your function with pointers aliased into unexpected places.

My question goes further than this observation. I ask if restrict permits optimization of the callers of your function too?

int f(const int *restrict p);
int caller(int *p) {
  int n=*p;
  f(p);
  return *p==n;
}

It would seem to me that this guarantees to the compiler that f will not modify *p, thus it can omit re-examining *p and the comparison, and can safely return 1. Neither Clang nor GCC do this optimization.

I know it's OK that they don't optimize the caller. I am asking whether the are permitted to optimize. (This is primarily a question about the semantics of my program, not about performance.)


Solution

  • In this code:

    int f(const int *restrict p);
    int caller(int *p) {
      int n=*p;
      f(p);
      return *p==n;
    }
    

    the compiler cannot conclude that *p does not change during execution of f because the code below shows a definition of f and a call to caller in which the behavior is defined (does not violate the restrict requirements) yet the value of *p changes during execution of f:

    extern int m[2];
    
    int f(const int * restrict p)
    {
        m[0] = 3;
        return p[1];
    }
    
    void bar(void)
    {
        caller(m);
    }
    

    When f is called in this case, p points to m[0], so *p is m[0]. Since f changes m[0], the value of *p changes during execution of f, but this does not violate the restrict requirements because the restrict definition only imposes requirements if p is used, directly or indirectly, to access the object. C 2018 6.7.3.1 4 says:

    During each execution of B, let L be any lvalue that has &L based on P. If L is used to access the value of the object X that it designates, and X is also modified (by any means), then the following requirements apply:…

    In the above f, m[0] is accessed only through the lvalue m[0] and not through any lvalue whose address is based on p. So the prerequisite condition for restrict never occurs for m[0], so it imposes no constraints on whether or how m[0] is modified.