Недавно я написал довольно простую ассемблерную функцию.
#[inline(always)]
pub fn usize_raw_load_acquire(dst: &mut usize, src: *const usize) {
use std::arch::asm;
assert!(src.is_aligned());
debug_assert!(!src.is_null());
#[cfg(target_arch = "x86_64")]
unsafe {
// In x86, things are properly ordered by default, and these operations
// are atomic!
asm! {
"mov rax, [{src}]",
"mov [{dst}], rax",
src = in(reg) src,
dst = in(reg) dst,
out("rax") _,
options(nostack, preserves_flags),
}
}
#[cfg(not(target_arch = "x86_64"))]
compile_error!("unsupported arch");
}
Что интересно (возможно), так это то, что я пометил его как безопасный...
Чтение указателей пугает по многим причинам, но я не понимаю, как любая из этих причин применима здесь! Однако давайте просто сосредоточимся на деталях происхождения и проигнорируем другие мои утверждения о том, что делает этот код.
По сути, я считаю, что src
может быть указателем на что угодно... и его можно безопасно читать в любой среде (например, даже если &mut
существует для того же объекта)
Почему я могу ошибаться?
Я изо всех сил пытаюсь понять, как чтение usize
из NonNull::dangling().as_ptr()
можно назвать безопасным.
даже если
&mut
существует для того же объекта
Эта часть определенно неверна. Rust не определяет уникальность ссылок &mut
только внутри Абстрактной машины; он определен и вне его — например, FFI тоже должен их соблюдать, и inline asm точно так же.
В частности, единственное, что определено в правилах псевдонимов , это то, что они следуют, по крайней мере, ноалиасам LLVM (хотя они и более строгие), а LLVM определяет это как:
Это указывает на то, что к ячейкам памяти, доступ к которым осуществляется через значения указателя на основе аргумента или возвращаемого значения, также не осуществляется доступ во время выполнения функции через значения указателя, не основанные на аргументе или возвращаемом значении.
Вы можете видеть, что это определяется с точки зрения доступа к памяти, а не чтения IR.
В практическом аспекте LLVM может оптимизировать другой код, исходя из предположения, что значение не считывается (например, перезаписать сохраненное значение), и это может привести к поломке другого кода и/или вашего кода.
См. также UCG#331 — Может ли встроенное чтение ассемблера читать uninit и нарушать noalias?.
Остальные инварианты, вероятно, в порядке. Если вы передадите неверный указатель, процессор перехватит его, но он не находится внутри абстрактной машины, поэтому Rust не делает никаких предположений по этому поводу. Это похоже на вызов (через FFI) на язык, на котором нет UB.
Даже игнорируя указатели на недопустимые адреса, предположим, что у вас есть
src_ref
, который является&mut
по тому же адресу, что иsrc
, если вы это сделаете:*src_ref = 42; usize_raw_load_acquire (&mut dst, src); *src_ref = 0;
значение вdst
не определено. На практике вполне вероятно, что он будет42
в отладочных сборках, а все, что было вsrc
до этого кода, в релизных сборках. Но это тоже не гарантировано, теоретически может случиться что угодно.