2

While there are many examples [1][2][3] that address how the restrict keyword works, I am not completely sure if the restrictified relation is transitive on the pointers that it can point to. For instance, the following code declares a structure that contains an integer and a pointer to an integer.

typedef struct container_s {
  int x;
  int *i;
} container_s;


int bar(container_s *c, int *i) {
  int* tmp = c->i;
  *tmp = 5;      
  *i = 4;
  return *tmp;
}

int main(){
  return 0;
}

Does the compiler need an extra load instruction for the last access of tmp (the returned value) because it cannot infer that *i and *tmp do not alias?

If so, would this new definition fix that load?

int bar(container_s *c, int* restrict i) { ... }

EDIT

This case int bar(container_s *c, int * restrict i) { ... } removes the extract load when I produce LLVM IR (clang -S -O3 -emit-llvm). However, I do not understand why the next two modifications do not remove that final load when:

  1. I update the definition of the function (is the restrict transitively considered for c->i?) to:

    int bar(container_s * restrict c, int *i) { ... }
    
  2. I update the structure as below (Why cannot the compiler infer that there is no need for an extra load?):

    typedef struct container_s {
      int x;
      int * restrict i;
    } container_s;
    
    int bar(container_s *c, int *i) { ... }
    
usr1234567
  • 21,601
  • 16
  • 108
  • 128
Kiko Fernandez
  • 857
  • 10
  • 26
  • 1
    `return *tmp;` The function should return void ( := not return a value ) – joop Dec 12 '16 at 16:53
  • 4
    Did you mean to have `container_s * restrict c` since you are asking about whether it is transitive, but your initial code does not have `restrict` anywhere? – Arkku Dec 12 '16 at 16:55
  • How compilers will *in general* turn your C code into machine code is not answerable subject material. `restrict` speaks to (non-)aliasing via pointers, and the manner, if any, in which a compiler handles accesses via a `restrict`-qualified pointer differently from access via an unqualified pointer is an aspect of the implementation of that compiler. – John Bollinger Dec 12 '16 at 17:39
  • @joop thanks, I will update the code shown above – Kiko Fernandez Dec 12 '16 at 21:25

2 Answers2

2

"would this new header fix that load?", --> No. The restrict refers to i, and accessing its fields:

... requires that all accesses to that object use, directly or indirectly, the value of that particular pointer... C11 §6.7.3 8

but does not extend to qualify those fields when they in turn are used to access other data.

#include<stdio.h>

typedef struct container_s {
  int x;
  int *i;
} container_s;


int bar(container_s * c, int* restrict  i) {
  int* tmp = c->i;
  *tmp = 5;
  *i = 4;
  return *tmp;
}

int main(void) {
  int i = 42;
  container_s s = { 1, &i };

  printf("%d\n", bar(&s, &i));
  printf("%d\n", i);
  printf("%d\n", *(s.i));
}

Output

4
4  
4
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • Yes, the compiler is not required to emit different code in the restrict-qualified case than in the non-qualified case, but I think the formal definition of `restrict` (6.7.3.1) permits it to do so in the OP's case. – John Bollinger Dec 12 '16 at 19:38
  • @JohnBollinger Agree on first part of [comment](http://stackoverflow.com/questions/41105620/is-the-c-restrict-qualifier-transitive/41106295?noredirect=1#comment69420826_41106295). Unclear on "permits it to do so in the OP's case." Do you mean OP's `void bar(container_s * c, int* i)` or `void bar(container_s * c, int* restrict i)`? – chux - Reinstate Monica Dec 12 '16 at 19:46
  • I mean that in the restrict-qualified case, the compiler is permitted to assume without checking that `*i` does not alias `*tmp`. As such, the output of your program could be `5`, `4`, `4`. Or in fact, it could be anything else, too, because the program does not conform. – John Bollinger Dec 12 '16 at 19:52
  • @JohnBollinger As OP only considered adding `restrict` to one pointer `i` (unusual IMO), code should be able to assume that `*i` is not aliased by _anything_. This does not directly imply `*tmp` is not aliased by anything - (`*c` is bigger than `*i` if that makes a diff.). Using `restrict` for all or none of the augment pointers in a function makes sense. Using for a subset is more difficult to assess issues like this. – chux - Reinstate Monica Dec 12 '16 at 21:32
  • Yes, the compiler must not assume that `*tmp` has zero aliases, but it *can* assume that `*tmp` does not have `restrict`-qualified **`*i`** as an alias. It can furthermore observe that the only write operation between setting `*tmp` and evaluating it to get the return value is the write to `*i`. It can thus assume that `*tmp` has not changed since it was set, and therefore avoid reading it back. It's true, however, that that conclusion would be clearer if both arguments were `restrict`-qualified, and I agree with your position that that would be a better approach here. – John Bollinger Dec 12 '16 at 21:57
  • @JohnBollinger Detail: AFAIK, `restrict ` is not only a "write operation" restriction, but an "access operation"restriction. So accesses to `*i`, read or write, only happens through `i`. – chux - Reinstate Monica Dec 12 '16 at 22:20
  • 1
    Yes, `restrict` imposes an any-access restriction, though that restriction applies only if (i) the restricted pointer is used to access the object to which it points, and (ii) the object addressed by the restricted pointer is modified within the scope of the restricted pointer (by any means). Those criteria are satisfied on every call to `bar()` in the OP's restrict-qualified example. The compiler may therefore conclude that `*tmp` and `*i` do not alias each other, and may use that conclusion to inform optimization decisions. – John Bollinger Dec 12 '16 at 22:43
2

I'm having trouble seeing how transitivity applies here, but I can speak to your example.

Does the compiler need an extra load instruction for the last access of tmp (the returned value) because it cannot infer that *i and *tmp do not alias?

The compiler indeed cannot safely infer that *i and *tmp do not alias in your original code, as you have aptly demonstrated. It does not follow that the compiler specifically needs emit the load instruction implied by the abstract machine semantics of the * operator, but it does need to take care to deal with the aliasing issue somehow.


If so, would [restrict-qualifying parameter i] fix that load?

Adding restrict-qualification to parameter i in the function definition places the following additional requirement on the behavior of the program (derived from the text of C2011, 6.7.3.1/4): during each execution of bar(), because i is (trivially) based on i, and *i is used to access the object it designates, and that designated object is modified during the execution of bar() (via *i at least), every other lvalue used to access the object designated by *i shall also have its address based on i.

*tmp is accessed, and its address, tmp, is not based on i. Therefore, if i == tmp (that is, if on some call, i == c->i) then the program fails to conform. Its behavior is undefined in that case. The compiler is free to emit code that assumes the program conforms, so in particular, in the restrict-qualified case it can emit code that assumes both that the statement

  *i = 4;

does not modify *tmp, and that the statement

  *tmp = 5;

does not modify *i. Indeed, it seems consistent with the definition and express intent of restrict that compilers be free to make precisely those assumptions.

In particular, if the compiler chooses to handle the possibility of aliasing in the original code by performing the possibly-redundant load of *tmp, then in the restrict-qualified version it might choose to optimize by omitting that load. However, the resulting machine code is by no means required to differ in any way between the two cases. That is, you cannot, in general, rely on the compiler to make use of all the optimizations available to it.

Update:

The followup questions ask why clang does not perform a particular optimization under particular circumstances. Before anything else, it is essential to reiterate that C compilers have no responsibility whatever for performing any particular optimization that may be possible for given source code, except as they themselves document. Therefore, one generally cannot draw any conclusions from the fact that a given optimization is not performed, and it is rarely useful to ask why a given optimization was not performed.

About as far as you can go -- and I am interpreting the questions in this light -- is to ask whether the optimization in question is one that a conforming compiler could have performed. In this case the standard underscores that by taking the unusual step of clarifying that restrict imposes no optimization obligation on implementations:

A translator is free to ignore any or all aliasing implications of uses of restrict.

(C2011, 6.7.3.1/6)

With that said, on to the questions.

  1. In this code variant, *tmp is an lvalue whose address is based on restrict-qualified pointer c. The object it designates is accessed via that lvalue within the scope of the function, and also modified within that scope (via *tmp, so the compiler can certainly see it). The address of *i is not based on c, so the compiler is free to assume that *i does not alias *tmp, just as in the original question.

  2. This case is different. Although it is permitted to restrict-qualify struct members, restrict has effect only when it qualifies an ordinary identifier (C2011, 6.7.3.1/1), which struct member names are not (C2011, 6.2.3). In this case, restrict has no effect, and to ensure conforming behavior, the compiler must account for the possibility that c->i and *i (and *tmp) are aliases.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157