-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Pointer equality across allocations hits poorly specified parts of LLVM #85968
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
To clarify the bug, since I misunderstood it at first, if the |
While LangRef does not explicitly say so, it is our working assumption that pointer equality is integer equality and comparison of pointers of different provenance is not poison. Alive2 was recently updated with these semantics. |
This seems to motivate that LLVM does not have these semantics today: bool always_false(void) {
int* p = malloc(4);
int q;
return p < &q;
} Clang is fully entitled to believe that this is UB, but LLVM emits the naive address comparison. All seems well? There is probably an overlap here between alias analysis feeding back into pointer equality (i.e. pointers do not alias, so the user cannot observe them being equal) that does not extend to order comparisions. Maybe it's sufficient to believe that It seems a bit difficult to disentangle general alias analysis from pointer equality, but such as it is, I think. |
Is there a better demonstration of actual unsoundness? In my view, #[inline(never)]
pub fn both() -> (bool, bool) {
let (a, b) = (0, 0);
(eq_outline((&a as *const i32).wrapping_add(1), &b), eq_inline((&a as *const i32).wrapping_add(1), &b))
} doesn’t seem to produce inconsistent results. Where’s the actual problem? Edit: By the way, I wasn’t able to fully understand the explanation below the example. Nonetheless, if there is any form of UB involved, it would help a lot if there was a better example that demonstrates any actually weird or problematic behavior in a way that doesn’t require understanding LLVM internals. |
I don't think there's a good way to illustrate this without knowledge of LLVM internals. =( I can try to think of a more complex example, but I've only got so much free time- I'm hoping people who more actively work with LLVM directly than I do can definitively confirm whether Rust is emitting sane IR. (Compare: Rust having to emit |
@rustbot label +T-compiler |
Honestly, those LLVM bugs don't make me feel any better. It's pretty clear that we're not sure if It may be worth either closing this or punting it to UCG, since the Rust semantics are not nailed down yet anyways and this is much more a "what does LLVM do" question at this point. (It definitely seems like something to pay attention to given we have had nasty miscompilations around |
Which part of the C standard do you derive this from (specifically for
I don't think this is correct. To my knowledge, comparing pointers in LLVM is always safe (
I think it is pretty clear that consensus is forming towards "no, provenance may not affect pointer comparison". There are LLVM transformations that assume this, and the provenance TS also mandates it. In the relevant bugreports, LLVM devs have to my knowledge never used the argument that provenance should be taken into account for pointer comparison. In other words: yes, there are bugs, because a few of the LLVM transformations have been written before people started thinking properly about the semantics of LLVM IR. But I see no indication that LLVM actually desired to make pointer comparison anything other than what Rust needs it to be. |
These are bugs, there are other LLVM optimizations that explicitly assume that provenance is not transmitted through bitcast and ptrtoint. Se this blog post for a more lengthy explanation. So, overall -- I agree it is important to keep an eye on what LLVM is doing here, and maybe work with them to ensure what they do works for us, but I don't think this issue should carry the "unsound" tag. |
Untagged per apparent consensus that this is... not unsound, just really weird and definitely erroneous somewhere. I think this can stay open as a bug, tho', even if it's mostly defined by twiddling our thumbs and waiting for LLVM. |
Which concrete example given here is erroneous? The ones in the OP all seem fine. |
@mcy could you clarify what you think is wrong about the examples your posted? They all seem fine to me. Having different behavior between debug and release builds is completely expected. Allocation is non-deterministic, so different builds of the program (even different runs of the same build) can pick different physical addresses to actually place the allocations at. |
@RalfJung I think we can close this, if you think it's a non-issue (beyond tracking that LLVM doesn't suddenly do anything wrong, which I think you're already keeping an eye on). I think my concern was around Incidentally, when I read your blogpost last year, the impression I had was that this wasn't so much a bug in LLVM and more of poor wording in the LangRef that resulted in this emergent miscompilation between passes. In particular, these two lines under the pointer aliasing rules section concern me:
Of course, the joke is that no one actually understands the aliasing model. Fixing that is beyond the scope of this issue. =) |
This is correct. But LLVM is not C. :) I agree that it is worth keeping an eye out for any places where we rely on LLVM differing from C. But I don't think having this issue helps in that quest. Maybe we should make a dedicated list of the cases where we do that... though I don't know where to put that list such that we'll find it when needed. ;) So I'll close this issue then. Thanks for bringing it up, being diligent in these areas is certainly important so if you notice anything similar in the future, please do let us know. :) |
I mean, I would think the UCG repo is the least wrong place. Asserting stuff like "comparing pointers across provenances gives a well-defined but unspecified value" is the kind of stuff we'd want to do there, and tracking "we need LLVM to actually allow us to do this, since a C/C++ compiler wouldn't care". TBH, if something breaks spectacularly I'm sure you and eddyb will notice quite quickly. =) |
Yeah that sounds reasonable, so I opened rust-lang/unsafe-code-guidelines#292. |
While Rust defines raw pointer equality as a safe operation, it appears that this is not so in practice. Consider the
following reduced example:
(Godbolt)
On Rust 1.52.0 with
rustc -O
, this printsfalse, true
; this is entirely dependent on the inlining attrs on thetwo
eq
functions.always_false
is optimized to a constant return, whilealways_true
passes identicalpointers (
rsp + 4
, in this case) intoeq_outline
, which does a blind comparison.The LLVM LangRef is unfortunately silent on this, but it appears that the root of the problem is that Rust emits the following code for pointer equality:
While this looks fine, Clang emits the exact same IR for comparisons of
int*
, and Clang is entitled to (and, in fact, makes) this optimization, because poitner comparisons across provenance domains is UB in C/C++. This makes me believe this is true UB, and not implementation-defined behavior.In short, it is incorrect to lower pointer comparison, with Rust semantics, to
icmp eq T*
. I don't know of a workaround, since LLVM explicitly transmits provenance throughbitcast
andptrtoint
; in fact,icmp
of pointers is defined asicmp
of the pointers passed throughptrtoint
. The solution may simply be to add some kind of optimization barrier to LLVM; who knows?#54685 is a related but distinct issue, since it's about comparison of function pointers, which are inherently global variables; the fix in LLVM is only relevant to globals, AFAICT.
The text was updated successfully, but these errors were encountered: