首页 > 解决方案 > 从 mut ref 创建的指针上过度激进的 noalias?

问题描述

考虑以下内容(在 Rust >= 1.54 上):

pub fn assign_refs(i: &mut u32, j: &mut u32) -> u32 {
    *i = 42;
    *j = 7;
    *i
}

根据可变引用之间没有别名,这编译为:

        mov     dword ptr [rdi], 42
        mov     dword ptr [rsi], 7
        mov     eax, 42
        ret

现在考虑:

pub fn assign_ptrs(i: *mut u32, j: *mut u32) -> u32 {
    *unsafe {&mut *i} = 42;
    *unsafe {&mut *j} = 7;
    unsafe {*i}
}

(注意一次只存在一个可变引用,所以这不是未定义的行为 if i == j)。

由于指针可能有别名,最后一个表达式必须重新加载:

        mov     dword ptr [rdi], 42
        mov     dword ptr [rsi], 7
        mov     eax, dword ptr [rdi]
        ret

如果j指向 ,则下一个示例是未定义的行为i

pub fn assign_undefined_behavior_if_same(i: &mut u32, j: *mut u32) -> u32 {
    *i = 42;
    *unsafe {&mut *j} = 7;  // UB if j points to i, second mut ref.
    *i
}

因此,它编译为与 相同的代码assign_refs,返回“错误”值。


我的问题是关于:

pub fn assign_mixed(i: &mut u32, j: *mut u32) -> u32 {
    *i = 42;

    let i_ptr = i as *mut u32;
    std::convert::identity(i);  // *Not* a reborrow, a move and drop.

    // i no longer exists.
    // *i = 42; // use of moved value: `i`

    // At this point, why not the same as assign_ptrs?
    *unsafe {&mut *j} = 7;

    // Assumes that i_ptr is not aliased just because it came from a &mut?
    unsafe {*i_ptr}
}

这编译成与 相同的东西assign_refs,我觉得这很令人惊讶。

唯一别名引用i在函数中途结束。在那一点上,为什么i_ptrj不一样对待,就好像我们在assign_ptrs?指针允许别名,因此j可以指向i/i_ptr 并且 i不再存在。

作为参考,可以这样称呼:

fn test() {
    let mut i = 0;

    let mut i_ref = &mut i;
    let i_ptr = i_ref as *mut u32;
    assign_mixed(i_ref, i_ptr);
}

这是过度激进的 noalias 传播吗?

标签: rust

解决方案


Rust 遵循来自 LLVMnoalias的模型,请参阅Behavior认为 undefined&mut T

这表明通过基于参数或返回值的指针值访问的内存位置在函数执行期间也不会通过不基于参数或返回值的指针值访问。此保证仅适用于在函数执行期间以任何方式修改的内存位置。返回值的属性还具有下面描述的附加语义。调用者与被调用者共同承担确保满足这些要求的责任。

因此,如果我理解正确,这意味着只是i: &mut u32在参数列表中期望它应该没有别名。甚至使用辅助功能,例如:

pub unsafe fn assign_mixed(i: &mut u32, j: *mut u32) -> u32 {
    aux(i, j)
}

pub unsafe fn aux(i: *mut u32, j: *mut u32) -> u32 {
    *i = 42;
    *j = 7;
    *i
}

不会工作。

我认为拥有类似东西的唯一方法是使用UnsafeCell如下:

use std::cell::UnsafeCell;

pub unsafe fn assign_mixed(i: &UnsafeCell<u32>, j: *mut u32) -> u32 {
    *i.get() = 42;

    *j = 7;
    *i.get()
}

将产生所需的汇编代码。一定不要&mut UnsafeCell用于此。


推荐阅读