Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upRe-enable noalias annotations by default once LLVM no longer miscompiles them #54878
Comments
bstrie
added
A-LLVM
I-slow
C-enhancement
A-codegen
labels
Oct 6, 2018
This comment has been minimized.
This comment has been minimized.
|
I’m still working on figuring out the underlying issue. The interesting ticket is #54462. |
nagisa
self-assigned this
Oct 7, 2018
This comment has been minimized.
This comment has been minimized.
|
Using @nagisa's minimal reproduction: Minimised test case with no unsafe code (make sure to compile with 1 codegen unit!):fn linidx(row: usize, col: usize) -> usize {
row * 1 + col * 3
}
fn swappy() -> [f32; 12] {
let mut mat = [1.0f32, 5.0, 9.0, 2.0, 6.0, 10.0, 3.0, 7.0, 11.0, 4.0, 8.0, 12.0];
for i in 0..2 {
for j in i+1..3 {
if mat[linidx(j, 3)] > mat[linidx(i, 3)] {
for k in 0..4 {
let (x, rest) = mat.split_at_mut(linidx(i, k) + 1);
let a = x.last_mut().unwrap();
let b = rest.get_mut(linidx(j, k) - linidx(i, k) - 1).unwrap();
::std::mem::swap(a, b);
}
}
}
}
mat
}
fn main() {
let mat = swappy();
assert_eq!([9.0, 5.0, 1.0, 10.0, 6.0, 2.0, 11.0, 7.0, 3.0, 12.0, 8.0, 4.0], mat);
}I was able to bisect LLVM's optimization passes to find the one causing the error. Running this command results in a working executeable (replace
While running this command results in a broken executable (the `assert_eq`` fails):
For this file, optimization |
This comment has been minimized.
This comment has been minimized.
|
Bisecting LLVM revisions (using llvmlab bisect) narrows it down to r305936-r305938, presumably r305938: [BasicAA] Use MayAlias instead of PartialAlias for fallback. Note that this is a pretty old change, from June 2017. Edit: Looking at the commit description, it seems likely that the bug existed prior to that, but was masked by BasicAA preventing later alias passes from running, which is what the commit fixed. The case involves checking aliasing between a pair of Edit2: Also, passing |
This comment has been minimized.
This comment has been minimized.
|
From a look at the pre-GVN IR, I feel like the root cause here might be in loop unrolling, depending on whether my understanding of how LLVM aliasing annotations work is correct. Consider a code like
where In LLVM IR this would go something like:
If we run this through
Note how all four copies of the loop use aliasing metadata over the same aliasing domain. Instead of being noalias within a single iteration, it's noalias across the whole function. Finally,
And this will result in incorrect results if It's possible to reproduce this issue from C with the following code:
With Clang 6.0 this prints |
This comment has been minimized.
This comment has been minimized.
|
I reduced it to a simple C test case (compile at -O3 and -O0 and compare output): #include <stdlib.h>
#include <stdio.h>
#include <assert.h>
__attribute__((always_inline))
static inline void copy(int *restrict a, int *restrict b) {
assert(a != b);
*b = *a;
*a = 7;
}
__attribute__((noinline))
void floppy(int mat[static 2], size_t idxs[static 3]) {
for (int i = 0; i < 3; i++) {
copy(&mat[i%2], &mat[idxs[i]]);
}
}
int main() {
int mat[3] = {10, 20};
size_t idxs[3] = {1, 0, 1};
floppy(mat, idxs);
printf("%d %d\n", mat[0], mat[1]);
}Note that if you remove What's happening is:
for (int i = 0; i < 3; i++) {
mat[idxs[i]] = mat[i%2]; mat[i%2] = 7;
}
mat[idxs[0]] = mat[0]; mat[0] = 7; /* from copy(&mat[0%2], &mat[idxs[0]]) */
mat[idxs[1]] = mat[1]; mat[1] = 7; /* from copy(&mat[1%2], &mat[idxs[1]]) */
mat[idxs[2]] = mat[0]; mat[0] = 7; /* from copy(&mat[2%2], &mat[idxs[2]]) */
But Well, it has to do with the way %8 = load i32, i32* %0, align 4, !tbaa !8, !alias.scope !10, !noalias !13
store i32 %8, i32* %7, align 4, !tbaa !8, !alias.scope !13, !noalias !10
store i32 7, i32* %0, align 4, !tbaa !8, !alias.scope !10, !noalias !13Normally, if a function is inlined multiple times, each copy gets its own unique IDs for alias.scope and noalias, indicating that each call represents its own 'inequality' relationship* between the pair of arguments marked However, in this case, first the function is inlined into the loop, then the inlined code is duplicated when the loop is unrolled – and this duplication does not change the IDs. Because of this, LLVM thinks none of the Amazingly, GCC also miscompiles this, with different output. (clang and GCC at -O0 both output * It's a bit more complicated than that, but in this case, since |
This comment has been minimized.
This comment has been minimized.
|
Heh, looks like I raced with @nikic to find the same explanation. Their test case is slightly nicer :) |
This comment has been minimized.
This comment has been minimized.
|
That's some really great timing ^^ We reached the same conclusion with nearly the same reduced test case at the same time :) To fix this, probably something along the lines of https://github.com/llvm-mirror/llvm/blob/54d4881c352796b18bfe7314662a294754e3a752/lib/Transforms/Utils/InlineFunction.cpp#L801 needs to be also be done in LoopUnrollPass. |
This comment has been minimized.
This comment has been minimized.
|
I've submitted an LLVM bug report for this issue at https://bugs.llvm.org/show_bug.cgi?id=39282. |
This comment has been minimized.
This comment has been minimized.
|
And – just mentioning this for completeness – I submitted a bug report to GCC since it also miscompiled my C test case: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87609 |
bstrie commentedOct 6, 2018
This issue tracks the undoing of the
-Zmutable-alias=nodefault introduced in #54639 on account of a bug in LLVM. cc @nagisa(Deja vu?)