Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Most C/C++ compilers don't propagate `restrict` after inlining, so you lose a lot of optimization potential even if you use `restrict`.


I had no idea about this, I had mostly assumed that a decent compiler would do this. There is no reason this can't be implemented in the future, right?


It is difficult to implement correctly because the rules for `restrict` in the spec are quite nonintuitive; it doesn't simply mean that the two pointers don't alias each other.

The guarantee provided by owned pointers in Rust is stronger than that of `restrict` in C, so we should be able to propagate the must-not-alias constraints better upon inlining.


In the interest of full disclosure, I should point out that the Rust compiler currently isn't conveying aliasing information to LLVM at all (or if it is, it's very minimal). If anyone's interested in implementing it, it would be both an interesting project and immediately catapult you into Rust godhood. :)


This is very interesting! Do you have some code examples or deeper explanation of this by any chance ?


Well, I'm not a C language lawyer myself, so I'm merely going off of what I have heard from people who are, but consider the following C function:

  bool f(int *restrict a, int *b, bool should_modify) {
    if (should_modify) {
      *a = b;
    }
    return a == b;
  }
Does this function have defined behavior? It turns out that the C spec says that the aliasing restrictions imposed by `restrict` only apply if the memory object pointed to by the restricted pointer has an intervening modification in the scope of the `restrict`; otherwise, the pointers are allowed to alias. Therefore, this function does not have defined behavior for all inputs. If a and b point to valid memory objects and a != b, then the behavior of f is defined regardless of the value of should_modify. If a == b, then f only has defined behavior if should_modify is false.

Yes, this is arguably insane, but it's also the spec.


Can you describe in more details what you mean? An inlined function with restrict parameters is of course inlined as if the variables are restricted. Indeed, a test of an inline of an inline of an inline was still optimized (or rather, not guarded) per the restrict.


Consider the simple loop

  void f(int *a, const int *b, unsigned size) {
    for (unsigned i = 0; i < size; ++i) {
      a[i] = b[i] * 2;
    }
  }
This is the LLVM IR generated by `clang -Ofast -c -o - -emit-llvm -S`:

https://gist.github.com/zwarich/21cf3a0b2387302ea48b

Notice the `vector.memcheck` block, which is doing a dynamic aliasing check to decide whether to use the vectorized code. If I add `restrict`, e.g.

  void f(int *restrict a, const int *b, unsigned size) {
    for (unsigned i = 0; i < size; ++i) {
      a[i] = b[i] * 2;
    }
  }
then that dynamic check goes away:

https://gist.github.com/zwarich/48fd374becf5f891cb5a

However, if I put the vectorized loop in a function that gets inlined, e.g.

  static void f(int *restrict a, const int *b, unsigned size) {
    for (unsigned i = 0; i < size; ++i) {
      a[i] = b[i] * 2;
    }
  }

  void g(int *a, int *b, unsigned size) {
    f(a, b, size);
  }
the dynamic aliasing check reappears:

https://gist.github.com/zwarich/3e75f17de835dd9064ee

This is because LLVM's loop vectorizer runs after inlining and the standard optimization passes (which are run in a bottom-up traversal of the SCCs of the callgraph, interleaved with inlining). The `noalias` attribute on function parameters, which is what represents `restrict` in LLVM IR, disappears upon inlining. Many other compilers do something similar, although some like IBM's XLC can preserve `restrict` upon inlining.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: