Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this redundant load/store optimization allowed in C99?

Consider the following:

extern void bar(int *restrict);

void foo(int *restrict p) {
  int tmp;
  bar(&tmp);
  *p = tmp;
}

Does C99 spec permit to optimize foo to the following?

extern void bar(int *restrict);

void foo(int *restrict p) {
  bar(p);
}

I tried gcc, Clang and Intel Compiler in -O3 mode and neither generated code which reflects the above optimization. This lead me to suspect that this optimization breaks the spec. If it is not allowed, where does it say so in the spec?

Note: my question is inspired by this SO question

like image 216
zr. Avatar asked Jun 04 '13 08:06

zr.


3 Answers

The answer is a definitive NO, this is not allowed.

Consider what happens if foo and bar are mutually recursive. For example, this implementation of bar:

void bar(int *restrict p)
{
    static int q;
    if (p == &q) {
        printf("pointers match!\n");
    } else if (p == NULL) {
        foo(&q);
    }
}

bar never dereferences p, so the restrict qualifier is irrelevant. It is obvious that the static variable q cannot have the same address as the automatic variable tmp in foo. Therefore, foo cannot pass its parameter back to bar, and the given optimization is not allowed.

like image 111
ridiculous_fish Avatar answered Oct 13 '22 21:10

ridiculous_fish


For the compiler to do the optimization, it must be sure that regardless of how bar is implemented, and how foo is called, well-defined behavior will not change.
Since the implementation of bar and the call to foo are unknown to the compiler, when it compiles foo, the theoretical existence of such a case is enough to prevent the optimization, even if it doesn't happen in reality.

Here's an example for such a situation. The important points are: 1. The parameter p points to write-only memory (e.g. memory mapped I/O).
2. bar is unsafe for use with a write-only pointer (maybe it writes it and then reads it back, expecting the same value).
The function foo is safe for use with a write-only pointer, because it only writes p. This is true even if bar is unsafe, because bar never gets p. With the suggested optiomization, bar does get p, which may cause trouble.

Here's an example for the file containing bar and the call to foo.

static int increment;

void bar(int *restrict p) {
    (*p)=0;
    if (increment) (*p)++;
}

void foo(int *restrict p);

int main(int ac, char **av) {
    int *p = get_io_addr();    /* Get a write-only memory mapped I/O address */
    increment = atoi(av[1]);
    foo(p);
    return 0;
}
like image 42
ugoren Avatar answered Oct 13 '22 20:10

ugoren


A brief reading of this SO question and this wikipedia enrty suggests that the restrict keyword can only have effect in the arguments of a function. However, reading the C99 standard, section 6.7.3.1 in particular, makes it clear that the restrict applies to the whole context in which the restrict is used. So by using

void foo(int *restrict p);

you are guaranteeing that the only reading of and writing into the memory block pointed to by p will be via p.

However, even with this information, when compiling foo, the compiler has no idea what bar will do with the information it is sent. For example, consider:

void bar (unsigned long long int *p) {
    *p = ((unsigned long long int) p) % 2000;
    }

The result is dependent on the value of the pointer set, which means that when compiling foo the optimisation assumption you suggest cannot definitively be made, as the result will be different if the optimisation you suggest is made.

like image 1
Neil Townsend Avatar answered Oct 13 '22 22:10

Neil Townsend