Change:
Playground link: Switching to debug causes it to print both addresses twice (expected behavior), but in release both functions are optimized into one, so and are given the same address. 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 for sending the original link which allowed me to discover this. Debug output: Release output: stable rustc 1.29.1 ( 2018-09-20)
Also worth noting (pointed out by 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.
It seems like LLVM starts comparing the pointer of bar to bar instead of comparing bar to foo. ~This very much looks like LLVM is messing up in some way.~ ! ! ~Unless LLVM is allowed to break function pointers entirely.~ Actually that may be what mergefunc is doing.
The problem is that they shouldn't produce an expression that is false in one statement and then true in the next. They should either always be equal or never be equal, but this code snippet shows that this is not always true. Can you include the source code from the playground and the compilation results (under release and debug) into your opening comment?
(came here from ) According to this could be caused by constant-folding to in LLVM (when and are different globals). IMO this optimization shouldn't apply to functions and/or any globals. cc
This is definitely a bug in LLVM, afaict - this expression should never be true in C++. It may be that they never set on functions?
Still happens with Still happens with Still happens with
Yeah Clang doesn't add anywhere as aggressively as rustc, and is the one that allows completely merging the functions (which is required for the comparison to return true).
seems like someone an optimization that assumes that functions are always distinct, even when . We might want to open a bug on LLVM?
I don't see why this would be an LLVM bug. The observed behavior seems perfectly in line with the meaning of : the address is not significant, so address comparisons can evaluate either way. If a frontend does not want that, it shouldn't add to functions whose address is observed. (Not saying that's what rustc should do, if it came down to it I would argue for the current behavior, just describing what would probably be a reasonable position of LLVM developers.)
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 implementation for function pointers does not satisfy the contract, which is something we should keep in mind. I suppose we could also have an analysis to see which functions have their address taken, and not set for them?
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 " wouldn't solve it. LLVM already does that, or more precisely, it adds to functions that are guaranteed to not have their address taken. This is only knowable for functions, so it's a significant downgrade from the status quo in non-LTO builds. It also can't distinguish e.g. between only putting a function in a vtable (which is not accessible to users and only ever used for calling) and reifying a function pointer in user code. The latter could be solved by an analysis in rustc, but the former is a fundamental limitation of separate compilation.
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 to most functions
I don't think we should stop using , and aside from this (overly, IMO) aggressive constant-folding optimization, I expect function pointers to behave as boringly as those obtained from , just that sometimes you might happen to have two pointers to the same machine code function, despite them originating from distinct Rust functions. That is, I expect the place where non-determinism happens, to be on reification to pointer. It's possible for two distinct Rust functions to map to the same machine code function, or the same Rust function to map to two machine code functions. That's what we don't guarantee - that mapping. is fine. Meanwhile, once you have a pointer, you should be able to expect it to behave in certain ways. If you have then I certainly expect and other properties to hold. And if you black box its creation you totally can. It's just this optimization.
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. Doesn't GVN rely on that? 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 being executed twice giving different results. Unless or changes when you compare it (which shouldn't happen, but technically is possible), you'd expect a comparison to be deterministic.
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 after their creation? That would likely fix this case, but it'd also kill LLVM's ability to replace dynamic calls by static ones.
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. Think about two sibling crates that both happen to instantiate - if you're generating machine code, how would you guarantee that you get the same function pointer?
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 type here, not at . The latter definitely should be deterministic. (However, LLVM has some bugs where it removes ptr-int-casts, leading to .)
I guess the confusing bit that led me to say "integer operations" is that LLVM uses for comparing pointers (albeit without casting to integers first). Also, I found : So it seems like patching LLVM to get the more boring behavior only involves adding an extra check for , right next to the and check. (Note that this problem also affects constant globals, not just functions)
Ah yes, is used for both integer and pointer comparisons. I never even thought about the fact that the might be for "integer". ;)
there already exists the guarantee that , no matter where these pointers were gotten. That's what inline linkage is for.
That guarantee does not hold in Rust. in one crate might be different from in another. But the thing that concerns us here is the opposite direction: whether LLVM can assume that for arbitrary function pointers and . That assumption is clearly wrong.
I was pointing out that that is a choice, not an inherent property of the space. We could absolutely choose to deduplicate functions across crates, and quite reasonably too, since the work has already been done for us by the C++ community.
I'm pretty sure the guarantee does not hold on Windows once DLLs are involved.
That's the argument for not marking functions in Rust inline, yes, but since DLLs are not really part of the same program as the program they are linked to, I feel like that's a distinct thing. You could argue that different Rust crates are different programs which communicate through (particularly fast) IPC reasonably, I think.
This issue was fixed in
Tagging as "needs test" -- do we have a minimal example?

Discussion:
<ins> // min-llvm-version: 12.0 // compile-flags: -C opt-level=3 // run-pass fn foo(_i: i32) -> i32 { 1 } fn bar(_i: i32) -> i32 { 1 } fn main() { let x: fn(i32) -> i32 = foo; let y: fn(i32) -> i32 = bar; let s1; if x == y { s1 = "same".to_string(); } else { s1 = format!("{:?}, {:?}", x, y); } let s2; if x == y { s2 = "same".to_string(); } else { s2 = format!("{:?}, {:?}", x, y); } assert_eq!(s1, s2); } </ins>