Description
While Rust defines raw pointer equality as a safe operation, it appears that this is not so in practice. Consider the
following reduced example:
#[inline]
pub fn eq_inline<T>(a: *const T, b: *const T) -> bool {
a == b
}
#[inline(never)]
pub fn eq_outline<T>(a: *const T, b: *const T) -> bool {
a == b
}
#[inline(never)]
pub fn always_false() -> bool {
let (a, b) = (0, 0);
eq_inline((&a as *const i32).wrapping_add(1), &b)
}
#[inline(never)]
pub fn always_true() -> bool {
let (a, b) = (0, 0);
eq_outline((&a as *const i32).wrapping_add(1), &b)
}
pub fn main() {
println!("{}, {}", always_false(), always_true())
}
On Rust 1.52.0 with rustc -O
, this prints false, true
; this is entirely dependent on the inlining attrs on the
two eq
functions. always_false
is optimized to a constant return, while always_true
passes identical
pointers (rsp + 4
, in this case) into eq_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:
define ptr_eq(i32* readnone %a, i32* readnone %b) i1 {
%eq = icmp eq i32 %a, %b
ret i1 %eq
}
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 through bitcast
and ptrtoint
; in fact, icmp
of pointers is defined as icmp
of the pointers passed through ptrtoint
. 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.