Description
The following code ought to be completely fine and UB-free:
use std::mem::transmute;
#[cfg(target_arch = "x86")]
use std::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
extern "C" fn no_target_feature(_dummy: f32, x: __m256) {
let val = unsafe { transmute::<_, [u32; 8]>(x) };
dbg!(val);
}
#[inline(always)]
fn no_target_feature_intermediate(dummy: f32, x: __m256) {
no_target_feature(dummy, x);
}
#[target_feature(enable = "avx")]
unsafe fn with_target_feature(x: __m256) {
// Critical call: caller and callee have different target features.
// However, we use the Rust ABI, so this is fine.
no_target_feature_intermediate(0.0, x);
}
fn main() {
assert!(is_x86_feature_detected!("avx"));
// SAFETY: we checked that the `avx` feature is present.
unsafe {
with_target_feature(transmute([1; 8]));
}
}
There's some unsafe going on, but the safety comment explains why that is okay. We are even taking care to follow the target-feature related ABI rules (see #115476); all calls between functions with different target-features use the "Rust" ABI.
And yet, this prints (when built without optimizations)
[src/main.rs:9] val = [
1,
1,
1,
1,
538976288,
538976288,
538976288,
538976288,
]
The value got clobbered while being passed through the various functions.
Replacing inline(always)
by inline(never)
makes the issue disappear. But inline
attributes must never cause miscompilation, so there's still a soundness bug here.
I don't know if this is the MIR inliner (Cc @rust-lang/wg-mir-opt) or the LLVM inliner going wrong.
Here's an LLVM issue for the problem: llvm/llvm-project#70563