llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
29.38k stars 12.15k forks source link

InstCombine cannot blindly assume that inttoptr(ptrtoint x) -> x #33896

Open nunoplopes opened 7 years ago

nunoplopes commented 7 years ago
Bugzilla Link 34548
Version trunk
OS All
Blocks llvm/llvm-project#34577 llvm/llvm-project#39193
CC @comex,@majnemer,@vns-mn,@dwblaikie,@efriedma-quic,@fhahn,@hfinkel,@jensmaurer,@dobbelaj-snps,@aqjune,@RKSimon,@Meinersbur,@sunfishcode,@MattPD,@uecker,@RalfJung,@regehr,@rnk,@sanjoy,@rotateright,@yuanfang-chen

Extended Description

Example of an end-to-end miscompilation by clang of the following code involving ptrtoint:

$ cat c.c

include

void f(int, int);

int main() { int a=0, y[1], x = 0; uintptr_t pi = (uintptr_t) &x; uintptr_t yi = (uintptr_t) (y+1); uintptr_t n = pi != yi;

if (n) { a = 100; pi = yi; }

if (n) { a = 100; pi = (uintptr_t) y; }

(int )pi = 15;

printf("a=%d x=%d\n", a, x);

f(&x,y);

return 0; }

$ cat b.c void f(intx, inty) {}

$ clang -O2 c.c b.c -o foo

$ ./foo a=0 x=0

This result is wrong. The two possible outcomes are: a=0 x=15, and a=100 x=0.

The bug is in Instcombine that treats inttoptr(ptrtoint(x)) == x, which is incorrect. This transformation can only be done if x is dereferenceable for the accesses through inttoptr. Compare the following: clang -O0 -S -emit-llvm -o - c.c | opt -S -sroa clang -O0 -S -emit-llvm -o - c.c | opt -S -sroa -instcombine

Integer compares are replaces with pointer compares (wrong) and load/stores are changed from inttoptr to pointers directly (also wrong).

Test case by Gil Hur.

regehr commented 7 years ago

Johns-MacBook-Pro-2:code regehr$ clang -v clang version 6.0.0 (trunk 312932) Target: x86_64-apple-darwin16.7.0 Thread model: posix InstalledDir: /Users/regehr/llvm-install/bin

Johns-MacBook-Pro-2:code regehr$ clang -O2 alias4.c alias4-b.c Johns-MacBook-Pro-2:code regehr$ ./a.out a=0 x=0

Johns-MacBook-Pro-2:code regehr$ cat alias4.c

include

include

void f(int , int );

int main() { int a = 0, y[1], x = 0; //int a=0, x = 0, y[1]; uintptr_t pi = (uintptr_t)&x; uintptr_t yi = (uintptr_t)(y + 1); uintptr_t n = pi != yi;

if (n) { a = 100; pi = yi; }

if (n) { a = 100; pi = (uintptr_t)y; }

(int )pi = 15;

printf("a=%d x=%d\n", a, x);

f(&x, y);

return 0; }

Johns-MacBook-Pro-2:code regehr$ cat alias4-b.c void f(int x, int y) {}

david-xl commented 7 years ago

I could not reproduce the problem either (same IR)

llvmbot commented 7 years ago

Weird, I see "a=0 x=0" at -O2 using both 4.0 and 5.0 on OS X and Ubuntu 16.04 on x86-64.

Can you verify this still happen on ToT ? In the meanwhile, I'm going to try with 5.0 on my machine (see if that applies, and bisect in case).

regehr commented 7 years ago

Weird, I see "a=0 x=0" at -O2 using both 4.0 and 5.0 on OS X and Ubuntu 16.04 on x86-64.

llvmbot commented 7 years ago

Also

[davide@cupiditate bin]$ ./clang c.c -O0 -S -emit-llvm -o - | ./opt -S -sroa > pre.ll [davide@cupiditate bin]$ ./clang c.c -O0 -S -emit-llvm -o - | ./opt -S -sroa -instcombine > post.ll [davide@cupiditate bin]$ diff -u pre.ll post.ll [davide@cupiditate bin]$

Presumably this might have been fixed/hidden? Which revision are you at? I'm at clang version 6.0.0 (trunk 312918) (llvm/trunk 312925)

Side note, I had to add to the code to get it compile.

llvmbot commented 7 years ago

Reid, try this change:

int a = 0, y[1], x = 0; //int a=0, x = 0, y[1];

Doesn't seem to repro here as well (x86-64_linux) [same happens with John's suggested change].

[davide@cupiditate bin]$ ./clang c.c b.c -O1 -o foo && ./foo a=100 x=0 [davide@cupiditate bin]$ ./clang c.c b.c -O2 -o foo && ./foo a=100 x=0 [davide@cupiditate bin]$ ./clang c.c b.c -O3 -o foo && ./foo a=100 x=0

regehr commented 7 years ago

Reid, try this change:

int a = 0, y[1], x = 0; //int a=0, x = 0, y[1];

rnk commented 7 years ago

Personally I get "a=100 x=0" for this test case.

Does the soundness issue go away if we stop optimizing 'n' to 1? That seems like a much better fix, IMO. Integer/pointer casts are far more important to optimize through than

aqjune commented 2 years ago

FWIW: opt now has a --disable-i2p-p2i-opt flag that disables the roundtrip cast.

gonzalobg commented 6 months ago

Would it make sense to have a ptr2int2ptr(x) instruction that allows InstCombine to merge ptr2int(int2ptr(x)) into a single operation, that can be then lowered by a backend more efficiently, without dropping the ptr2int/int2ptr casts (which I agree is unsound) ?

RalfJung commented 6 months ago

What is the semantics of that operation? Why would a frontend not just emit x instead of ptr2int2ptr(x), if that operation has a semantics that permits such a transformation?

gonzalobg commented 6 months ago

Depending on your provenance model, ptr2int2ptr(x) would have the effect of escaping the pointer (e.g. in a PNVI-ae-udi-like model), while x would not (preventing some compiler reorderings that x would not).

RalfJung commented 6 months ago

Depending on your provenance model,

You mean, depending on which choice LLVM makes for its provenance mode? LLVM IR is a language with its own semantics, decisions like the provenance model have to be made by the LLVM project (as they require adjusting the optimizations to be conformant). Frontends don't get to choose the LLVM provenance model. Frontend languages will have their own provenance model and they have to ensure that it can be mapped into LLVM's (similar to how e.g. frontends have to map their notion of 'uninitialized memory' to what LLVM does).

ptr2int2ptr(x) would have the effect of escaping the pointer (e.g. in a PNVI-ae-udi-like model), while x would not (preventing some compiler reorderings that x would not).

That sounds like y = ptr2int2ptr(x); would be equivalent to ptr2int(x); y = x;. No need for a new operation.