Can restrict qualifier be used to hint to the compiler that external functions won't modify your memory?

94 views Asked by At

As a follow up to my previous question, consider the following code:

int f(int *p);

static inline int g0(int *p) {
  *p=0;
  f(NULL); // might modify *p
  return *p==0;
}
int caller0(int *q) { return g0(q); }

Above, after calling f, the compiler has to fetch *p and perform the comparison to 0, because p might alias memory that is also available to f. However:

static inline int g1(int *restrict p) {
  *p=0;
  f(NULL); // cannot modify *p
  return *p==0;
}
int caller1(int *q) { return g1(q); }

Because of restrict, the compiler can deduce that it is illegal for f to modify *p, thus it can just return 1, optimizing out the fetch and the comparison. Clang 17 does this optimization. However:

int caller2(int *q) {
  { int *restrict p=q;
    *p=0;
    f(NULL);
    return *p==0;
  }
}

This caller2 seems to me to be equivalent to caller1, just inlined by hand. The same Clang compiler did not optimize this (it emits the fetch and the comparison).

First question: Is the compiler allowed to optimize caller2?

Further:

int caller3(int *q) {
  ... // many lines of code
  { int *restrict p=q;
    *p=0;
    ... // do something useful
    f(q);
    if (*p==0) { ... } // do something useful
  }
  ... // many lines of code
}

Now this is the part I actually care about. If the compiler is allowed to optimize caller2, is it allowed to optimize caller3?

According to my reading of the standard, q is not "based on" p here, and *p is modified through the restrict-qualified pointer, therefore the compiler can assume that f does not modify *p (further; f isn't even allowed to read *q, so the compiler is even allowed to reorder the assignment *p=0 after the call to f (though f may legally read/write *(q+1))).

Is this correct? If it is, it suggests that restrict can be creatively used even inside the bodies of your functions to make various hints about aliasing to the compiler.

1

There are 1 answers

5
John Bollinger On

Because of restrict, the compiler can deduce that it is illegal for f to modify *p, thus it can just return 1, optimizing out the fetch and the comparison.

"Illegal" is not the correct term. The spec does not forbid such behavior. It simply washes its hands of the situation if f does modify *p. In that case, that particular call to g1(), and therefore the whole execution of the program, has undefined behavior. Thus (the modern interpretation goes) the compiler can produce a program that acts as if f does not modify *p, regardless of actual fact.

Is the compiler allowed to optimize caller2?

If we accept that the compiler is allowed to optimize caller1() as described, then yes, it is allowed to perform the analogous optimization on caller2(). But it is not obligated to do so.

If the compiler is allowed to optimize caller2, is it allowed to optimize caller3?

Yes, if we accept the argument allowing the optimization of caller2 as described, then the same argument allows the analogous optimization of caller3.

As you observe, q is not "based on" p. Since p has no linkage and neither its address nor the address of another object whose value is based on p is ever taken, the compiler can conclude that f() has no access to any pointer based on restrict-qualified p, and therefore that the behavior of the program is undefined if evaluation of f(q) modifies *p.

If it is, it suggests that restrict can be creatively used even inside of your functions to make various hints about aliasing to the compiler.

I guess you could characterize it that way. I'd inclined to say something more like "restrict can be used inside functions to invite the compiler to emit broken code for them".

In the example case, if you wanted to enable the compiler to optimize based on the assumption that f will not modify the data to which its argument points then it would be cleaner and clearer to declare its argument as a pointer to const data:

int f(const int *p);

If you can't do that, then the non-aliasing assumption you want to permit probably is not safe.

Overall, restrict doesn't really make sense for use on automatic variables. The compiler does not need your help to see the aliasing opportunities involving those, and suggesting that it make decisions based on a more limited view of those is asking for trouble.