-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Inconsistent behavior when comparing function pointers in release #54685
Comments
Also worth noting (pointed out by @memoryruins) that MIRI throws an error in the comparison saying it dereferenced a function pointer. |
We run mergefunc pass on release builds. Anyway, can you explain why this is a problem? Rust does not make any guarantee on pointer values. |
@ishitatsuyuki The problem is that they shouldn't produce an @rep-nop Can you include the source code from the playground and the compilation results (under release and debug) into your opening comment? |
|
This is definitely a bug in LLVM, afaict - this expression should never be true in C++. It may be that they never set |
|
@ubsan Yeah Clang doesn't add |
@rkruppe seems like someone added an optimization that assumes that functions are always distinct, even when |
I don't see why this would be an LLVM bug. The observed behavior seems perfectly in line with the meaning of |
Oh this is beautiful. ;) Finally we have a real-life example of pointer comparison being non-deterministic. It's not at all how I expected that example to look, though... Non-determinism does not mean that we have UB, so I do not think there is a huge problem here. But non-deterministic comparison is certainly surprising. It means, for example, that the I suppose we could also have an analysis to see which functions have their address taken, and not set |
What would be a problem would be if LLVM also assumes that equality is deterministic and duplicates 2 occurrences of the comparison. |
That is a very good point. I don't think there is anything preventing that, and in principle that could lead to unsoundness. However, this problem applies to all comparisons that might be non-deterministic, right? So "just don't use
LLVM already does that, or more precisely, it adds |
@arielb1 Ah right, forgot about that... so, is it UB under LLVM IR to take the address of a function tagged
True, but it is not clear if there are any. I cannot give a satisfactory definition of LLVM semantics that makes ptr comparison deterministic, but I also was unable to find an example that would demonstrate that it is not deterministic. |
I see no reason for that and it seems unnecessarily restrictive (again consider function pointers in vtables, their address is taken and must be considered to escape, but the actual address is never observed). It ought to be enough if comparisons, pointer->integer casts, and and anything else that leaks information about the address is undef or poison. |
Ack -- just taking a reference and calling it is fine indeed. However, in Rust, with fn ptr comparison being a safe operation, that doesn't really help us all that much. |
Yes, for Rust we may have to stop applying |
I don't think we should stop using That is, I expect the place where non-determinism happens, to be on reification to Meanwhile, once you have a pointer, you should be able to expect it to behave in certain ways. |
@arielb1 said:
In terms of "pure operations on SSA values", I think it's reasonable to expect that the same operation applied to the same inputs, would be always deterministic. Oh, I figured out why we can even observe the difference! Printing takes a reference, and LLVM doesn't know it's a read-only reference, and assumes the pointer can change. |
Especially if you have the statement |
That's only true if the operation is, well, deterministic. The game is still open about whether that is the case for pointer comparison (think: comparing one-past-the-end pointers). But those subtleties are likely not an issue here. Also one could make the same argument about casting a function to a fn ptr, I do not see a good reason why that would be any less deterministic than other operations. But, concretely, you seem to propose to pass fn ptrs through |
No, I'm not. I'm just saying that the resulting behavior is what I expect, for integer operations on
The compilation model means that we can't always easily deduplicate instances of the same function with the same type parameters, if that's what you mean. |
That's a different case though. Here, both casts are in the same function even. Seems reasonable to expect something deterministic there.
I do not understand what you are proposing then. Also, ptr equality is not the same as equality of the resulting integers. We are talking about comparison at |
@RalfJung I guess the confusing bit that led me to say "integer operations" is that LLVM uses Also, I found the code responsible for making the decision that the const-fold is valid: static ICmpInst::Predicate areGlobalsPotentiallyEqual(const GlobalValue *GV1,
const GlobalValue *GV2) {
auto isGlobalUnsafeForEquality = [](const GlobalValue *GV) {
if (GV->hasExternalWeakLinkage() || GV->hasWeakAnyLinkage())
return true;
// ...
if (!isGlobalUnsafeForEquality(GV1) && !isGlobalUnsafeForEquality(GV2))
return ICmpInst::ICMP_NE; So it seems like patching LLVM to get the more boring behavior only involves adding an extra check for |
Ah yes, |
@eddyb there already exists the guarantee that template <typename T>
void foo();
|
Lint unnamed address comparisons Functions and vtables have an insignificant address. Attempts to compare such addresses will lead to very surprising behaviour. For example: addresses of different functions could compare equal; two trait object pointers representing the same object and the same type could be unequal. Lint against unnamed address comparisons to avoid issues like those in rust-lang/rust#69757 and rust-lang/rust#54685. Changelog: New lints: [`fn_address_comparisons`] [#5294](#5294), [`vtable_address_comparisons`] [#5294](#5294)
Lint unnamed address comparisons Functions and vtables have an insignificant address. Attempts to compare such addresses will lead to very surprising behaviour. For example: addresses of different functions could compare equal; two trait object pointers representing the same object and the same type could be unequal. Lint against unnamed address comparisons to avoid issues like those in rust-lang/rust#69757 and rust-lang/rust#54685. changelog: New lints: [`fn_address_comparisons`] [#5294](#5294), [`vtable_address_comparisons`] [#5294](#5294)
This issue was fixed in https://reviews.llvm.org/D87123 |
Tagging as "needs test" -- do we have a minimal example? |
…ark-Simulacrum Add regression test for issue rust-lang#54685 Closes rust-lang#54685 Took the test from rust-lang#54685 and modified it a bit to use assertion. Made sure that the updated test catches the original issue on 1.50 by running on Compiler Explorer (https://godbolt.org/z/E64onYeT5).
Rollup of 11 pull requests Successful merges: - rust-lang#85054 (Revert SGX inline asm syntax) - rust-lang#85182 (Move `available_concurrency` implementation to `sys`) - rust-lang#86037 (Add `io::Cursor::{remaining, remaining_slice, is_empty}`) - rust-lang#86114 (Reopen rust-lang#79692 (Format symbols under shared frames)) - rust-lang#86297 (Allow to pass arguments to rustdoc-gui tool) - rust-lang#86334 (Resolve type aliases to the type they point to in intra-doc links) - rust-lang#86367 (Fix comment about rustc_inherit_overflow_checks in abs().) - rust-lang#86381 (Add regression test for issue rust-lang#39161) - rust-lang#86387 (Remove `#[allow(unused_lifetimes)]` which is now unnecessary) - rust-lang#86398 (Add regression test for issue rust-lang#54685) - rust-lang#86493 (Say "this enum variant takes"/"this struct takes" instead of "this function takes") Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
Playground link: https://play.rust-lang.org/?gist=62d362e2bf72001bd3f3c4a3ed0c842c&version=stable&mode=release&edition=2015
Switching to debug causes it to print both addresses twice (expected behavior), but in release both functions are optimized into one, so
x
andy
are given the same address. @CryZe took a look at the assembly, and LLVM removed the first comparison (and evaluated it to a constant false), but not the second, which then evaluates to true. This is very reminiscent of undefined behavior in C and C++.Thanks to @memoryruins for sending the original link which allowed me to discover this.
Debug output:
Release output:
stable rustc 1.29.1 (b801ae6 2018-09-20)
The text was updated successfully, but these errors were encountered: