Open llvmbot opened 7 years ago
It would be good to get some clarity here before the release.
There doesn't seem to be a compiler bug here: this code violates C's aliasing rules. A load from a i8* isn't allowed to alias a load from a struct.typeB
I looked this up, and the standard (c99) makes a specific exception for (unsigned) char, doesn't it? See quoted text below.
"An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
struct.typeB is "a type compatible with the effective type of the object", and uint8_t (i8) is a "character type". Can you help me explain the spec violation of my code?
Regardless, let's assume you're right just for the sake of argument. The standard recommended workaround for strict aliasing violations (type punning) is to use unions. If I remove all (2) casts in my code and replace them with unions, the assert still triggers. Therefore, I believe your analysis is incorrect and there is a compiler bug here.
$ cat /tmp/y.c
typedef struct typeA { int placeholder; } typeA; typedef struct typeB { int placeholder; typeA a; } typeB; typedef struct typeC { typeB b; } typeC; typedef struct typeD { int placeholder; typeC z[2]; } typeD; typedef struct typeE { typeB b; typeA a; } typeE; static void init2(typeE const s, typeB const b) { b->a = s->a; }
static void init1(typeD const t, uint8_t const mem_in, const ptrdiff_t sz) { uint8_t mem = mem_in; typeE s; uint8_t ptrs[2];
ptrs[0] = mem; mem += NUM_BYTES;
s.b = (union { typeB *b; uint8_t *u; }) { .u = ptrs[0] }.b;
ptrs[1] = mem;
s.a = NULL;
int b, e;
for (b = 0; b < 2; b++) {
t->z[b].b = s.b;
for (e = 0; e < 2; e++)
init2(&s, &t->z[b].b[e]);
s.b += 2;
}
printf("assert(%p == %p);\n", ptrs[1], s.b);
assert(ptrs[1] == (union { typeB *b; uint8_t *u; }) { .b = s.b }.u);
}
int main(void) { const ptrdiff_t sz = NUM_BYTES; typeD d = malloc(sizeof(typeD)); uint8_t const mem = malloc(sz); init1(d, mem, sz); return 0; }
$ clang-mp-4.0 --version clang version 4.0.0 (branches/release_40 292772) Target: x86_64-apple-darwin16.4.0 Thread model: posix InstalledDir: /opt/local/libexec/llvm-4.0/bin
$ gcc-mp-4.8 --version gcc-mp-4.8 (MacPorts gcc48 4.8.5_0) 4.8.5 Copyright (C) 2015 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ clang-mp-4.0 -o /tmp/y /tmp/y.c -O3 -Wall && /tmp/y assert(0x7fd63e4028a0 == 0x7fd63e4028a0); Assertion failed: (ptrs[1] == (union { typeB b; uint8_t u; }) { .b = s.b }.u), function init1, file /tmp/y.c, line 34. Abort trap: 6
$ clang-mp-4.0 -o /tmp/y /tmp/y.c -O1 -Wall && /tmp/y assert(0x7fd8be4028c0 == 0x7fd8be4028c0);
$ gcc-mp-4.8 -o /tmp/y /tmp/y.c -O3 -Wall && /tmp/y assert(0x7ff1224028a0 == 0x7ff1224028a0);
Sorry that was with Apple's version of clang, I keep making that mistake, sorry about that. But the same happens with the 4.0 release of clang-upstream from Macports:
$ clang-mp-4.0 -o /tmp/x -O3 -fno-strict-aliasing /tmp/x.c && /tmp/x assert(0x7fb5864028a0 == 0x7fb5864028a0); Assertion failed: (ptrs[1] == (uint8_t *) s.b), function init1, file /tmp/x.c, line 34. Abort trap: 6
Try adding -fno-strict-aliasing to your optimization flags.
The assert still triggers when using that flag:
$ gcc -o /tmp/x -O3 -fno-strict-aliasing /tmp/x.c && /tmp/x assert(0x7fa8a7c028a0 == 0x7fa8a7c028a0); Assertion failed: (ptrs[1] == (uint8_t *) s.b), function init1, file /tmp/x.c, line 34. Abort trap: 6
Maybe the compiler should warn about that? Even with -Wall, I get no warnings at all.
Generating warnings for this is not easy, will very likely have false positives, and will definitely not catch all aliasing abuses. Plus it would break many existing projects out there. :)
Any suggestions on how to change the code so it's aliasing-compliant yet still allows me to compare pointers?
Try adding -fno-strict-aliasing to your optimization flags.
Maybe the compiler should warn about that? Even with -Wall, I get no warnings at all. Any suggestions on how to change the code so it's aliasing-compliant yet still allows me to compare pointers?
There doesn't seem to be a compiler bug here: this code violates C's aliasing rules. A load from a i8* isn't allowed to alias a load from a struct.typeB, so the compiler throws away the pointer comparison.
Here's the IR:
define i32 @main() local_unnamed_addr #0 { %1 = tail call i8 @calloc(i64 1, i64 64) %2 = getelementptr inbounds i8, i8 %1, i64 64 %3 = getelementptr inbounds %struct.typeB, i8 %1, i64 4 %4 = tail call i32 (i8, ...) @printf(i8 getelementptr inbounds ([19 x i8], [19 x i8] @.str, i64 0, i64 0), i8 %2, %struct.typeB %3) #3 tail call void @__assert_rtn(i8 getelementptr inbounds ([6 x i8], [6 x i8] @func.init1, i64 0, i64 0), i8 getelementptr inbounds ([9 x i8], [9 x i8] @.str.1, i64 0, i64 0), i32 34, i8 getelementptr inbounds ([27 x i8], [27 x i8] @.str.2, i64 0, i64 0)) #4 unreachable }
And some further info, it goes away when reducing -O from 2 to 1:
$ clang-mp-4.0 -o /tmp/x -O2 /tmp/x.c
$ /tmp/x assert(0x7fcc144028a0 == 0x7fcc144028a0); Assertion failed: (ptrs[1] == (uint8_t *) s.b), function init1, file /tmp/x.c, line 34. Abort trap: 6
$ clang-mp-4.0 -o /tmp/x -O1 /tmp/x.c
$ /tmp/x assert(0x7f8b0c600060 == 0x7f8b0c600060);
$ clang-mp-4.0 -o /tmp/x -O0 /tmp/x.c
$ /tmp/x assert(0x7fe6194028c0 == 0x7fe6194028c0);
Hey guys,
I'm really terribly sorry to reopen this bug. Let me explain: Apple just released XCode 8.3 (with their "clang 8.1") and it fixed the compiler crash. However, the compiler (still?) miscompiles the code. What's more interesting is that the miscompilation reproduces in upstream clang! I'm hoping this time the bug report is acceptable.
(Below, "clang-mp-4.0" is clang-4.0 installed from MacPorts, and gcc-mp-4.8 is gcc-4.8 installed from MacPorts.)
===
$ clang-mp-4.0 --version clang version 4.0.0 (branches/release_40 292772) Target: x86_64-apple-darwin16.4.0 Thread model: posix InstalledDir: /opt/local/libexec/llvm-4.0/bin
$ cat /tmp/x.c
typedef struct typeA { int placeholder; } typeA; typedef struct typeB { int placeholder; typeA a; } typeB; typedef struct typeC { typeB b; } typeC; typedef struct typeD { int placeholder; typeC z[2]; } typeD; typedef struct typeE { typeB b; typeA a; } typeE; static void init2(typeE const s, typeB const b) { b->a = s->a; }
static void init1(typeD const t, uint8_t const mem_in, const ptrdiff_t sz) { uint8_t mem = mem_in; typeE s; uint8_t ptrs[2];
ptrs[0] = mem; mem += NUM_BYTES;
s.b = (typeB *) ptrs[0];
ptrs[1] = mem;
s.a = NULL;
int b, e;
for (b = 0; b < 2; b++) {
t->z[b].b = s.b;
for (e = 0; e < 2; e++)
init2(&s, &t->z[b].b[e]);
s.b += 2;
}
printf("assert(%p == %p);\n", ptrs[1], s.b);
assert(ptrs[1] == (uint8_t *) s.b);
}
int main(void) { const ptrdiff_t sz = NUM_BYTES; typeD d = malloc(sizeof(typeD)); uint8_t const mem = malloc(sz); init1(d, mem, sz); return 0; }
$ clang-mp-4.0 -o /tmp/x -O3 /tmp/x.c
$ /tmp/x assert(0x7f84144028a0 == 0x7f84144028a0); Assertion failed: (ptrs[1] == (uint8_t *) s.b), function init1, file /tmp/x.c, line 34. Abort trap: 6
$ gcc-mp-4.8 --version gcc-mp-4.8 (MacPorts gcc48 4.8.5_0) 4.8.5 Copyright (C) 2015 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc-mp-4.8 -o /tmp/x -O3 /tmp/x.c
$ /tmp/x assert(0x7f7f944028a0 == 0x7f7f944028a0);
For future reference, you can try compiling your code here: https://godbolt.org/g/aQ3vUm
To see if you can induce any open-source clang release to reproduce the crash (I couldn't - although I had to modify the code to get it to compile it all there).
If you can show a failure there, we could potentially bisect that failure to some commit in trunk.
OK, thanks for testing, sorry for the noise... How shall I close it?
Cannot reproduce with 4.0 and ToT, so it's Apple bug.
@llvm/issue-subscribers-c
The first test case reduced by C-Reduce:
typedef struct {
int *b
} typeE;
*init1_mem_in;
main_b;
main() {
char *mem = init1_mem_in;
typeE s;
mem += sizeof(0);
s.b = init1_mem_in;
int e;
main_b = 0;
for (; main_b < 1; main_b++) {
for (; e;)
init2(&s);
s.b++;
}
if (mem == s.b)
__assert_fail();
}
Extended Description
(I originally filed this in Apple's bug reporting system as 31009924, but I'm not getting any response there, so I figured I would post it here instead. If this is the wrong place, I'm sorry.)
Using the provided source file, the compiler (Apple LLVM version 8.0.0 (clang-800.0.42.1)) crashes.
Please note that I'm fully aware that the provided code is not meaningful, it's just a very condensed version of the actual source file that causes the compiler crash.
Steps to Reproduce:
$ cat /tmp/test.c
include
include
include
typedef struct typeA { int placeholder; } typeA; typedef struct typeB { int placeholder; typeA a; } typeB; typedef struct typeC { typeB b; } typeC; typedef struct typeD { int placeholder; typeC z[2]; } typeD; typedef struct typeE { typeB b; typeA a; } typeE; static void init2(typeE const s, typeB const b) { b->a = s->a; }
define NUM_BYTES (sizeof(typeB) 2 2)
static void init1(typeD const t, uint8_t const mem_in, const ptrdiff_t sz) { uint8_t mem = mem_in; typeE s; uint8_t ptrs[2], **ptr = ptrs;
}
int main(void) { const ptrdiff_t sz = NUM_BYTES; uint8_t *const mem = malloc(sz); init1(NULL, mem, sz); return 0; }
$ gcc -o /tmp/test.o -c /tmp/test.c -O3 clang: error: unable to execute command: Segmentation fault: 11 clang: error: clang frontend command failed due to signal (use -v to see invocation) Apple LLVM version 8.0.0 (clang-800.0.42.1) Target: x86_64-apple-darwin16.4.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin clang: note: diagnostic msg: PLEASE submit a bug report to http://developer.apple.com/bugreporter/ and include the crash backtrace, preprocessed source, and associated run script. clang: note: diagnostic msg:
PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT: Preprocessed source(s) and associated run script(s) are located at: clang: note: diagnostic msg: /var/folders/fz/vjyfg5z12vj759mhd1t27r_m0000gn/T/test-287e75.c clang: note: diagnostic msg: /var/folders/fz/vjyfg5z12vj759mhd1t27r_m0000gn/T/test-287e75.sh clang: note: diagnostic msg: