Given that TBI(armv8.5a+)/UAI(amd zen4)/LAM(intel) are coming to real life, I wonder how should a user actually use these features in Rust. (Let's consider use level usage rather than compiler optimizers).
For instance, if you have Apple Silicon devices (or any other device fulfills armv8.5a profile with TBI enabled kernel), the following code should work:
use std::mem::{size_of, ManuallyDrop};
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
pub trait PointerLike: Sized {}
impl<T> PointerLike for &'_ T {}
impl<T> PointerLike for &'_ mut T {}
impl<T> PointerLike for Box<T> {}
impl<T> PointerLike for Rc<T> {}
union TBITag<T: PointerLike> {
tag: [u8; size_of::<*const u8>()],
pointer: std::mem::ManuallyDrop<T>,
}
impl<T: PointerLike> TBITag<T> {
fn mask(&self) -> u8 {
unsafe {
if cfg!(target_endian = "big") {
self.tag[0]
} else {
self.tag[core::mem::size_of::<*const u8>() - 1]
}
}
}
fn set_mask(&mut self, x: u8) {
unsafe {
if cfg!(target_endian = "big") {
self.tag[0] = x;
} else {
self.tag[core::mem::size_of::<*const u8>() - 1] = x;
}
}
}
fn new(data: T) -> Self {
Self {
pointer: ManuallyDrop::new(data),
}
}
}
impl<T: PointerLike> Drop for TBITag<T> {
fn drop(&mut self) {
self.set_mask(0);
unsafe {
ManuallyDrop::drop(&mut self.pointer);
}
}
}
impl<T: PointerLike + Deref> Deref for TBITag<T> {
type Target = T::Target;
fn deref(&self) -> &Self::Target {
unsafe { &self.pointer }
}
}
impl<T: PointerLike + DerefMut> DerefMut for TBITag<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut self.pointer }
}
}
fn main() {
let mut x = TBITag::new(Box::new(5));
x.set_mask(170);
println!("x = {}", *x);
println!("tag = {}", x.mask());
}
However, if you run this with miri
, it will complain about UB (of cuz!).
Leaving alone the miri
problem, I wonder if TBI is actually "safe" with current implementation. To me, wrapping the pointer deeply into Union and ManuallyDrop should have already forbidden the compiler from optimizing the code in unexpected ways, but, emmm, it is "UB" after all!