据我所知,引用/指针别名会阻碍编译器生成优化代码的能力,因为它们必须确保在两个引用/指针确实别名的情况下生成的二进制行为正确。例如,在下面的C代码中,

void adds(int *a, int *b) {
    *a += *b;
    *a += *b;
}

当clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)编译时,它会发出-O3标志

0000000000000000 <adds>:
   0:    8b 07                    mov    (%rdi),%eax  # load a into EAX
   2:    03 06                    add    (%rsi),%eax  # load-and-add b
   4:    89 07                    mov    %eax,(%rdi)  # store into a
   6:    03 06                    add    (%rsi),%eax  # load-and-add b again
   8:    89 07                    mov    %eax,(%rdi)  # store into a again
   a:    c3                       retq

这里代码存储回(%rdi)两次,以防int *a和int *b别名。

当我们显式地告诉编译器这两个指针不能与restrict关键字别名:

void adds(int *restrict a, int *restrict b) {
    *a += *b;
    *a += *b;
}

然后Clang将发出一个更优化的版本,有效地执行*a += 2 * (*b),如果(正如restrict所承诺的那样)*b没有通过赋值给*a而被修改,则等价:

0000000000000000 <adds>:
   0:    8b 06                    mov    (%rsi),%eax   # load b once
   2:    01 c0                    add    %eax,%eax     # double it
   4:    01 07                    add    %eax,(%rdi)   # *a += 2 * (*b)
   6:    c3                       retq

由于Rust确保(除了不安全的代码)两个可变引用不能别名,我认为编译器应该能够发出更优化的代码版本。

当我测试下面的代码并使用-C opt-level=3——emit obj用rustc 1.35.0编译时,

#![crate_type = "staticlib"]
#[no_mangle]
fn adds(a: &mut i32, b: &mut i32) {
    *a += *b;
    *a += *b;
}

它生成:

0000000000000000 <adds>:
   0:    8b 07                    mov    (%rdi),%eax
   2:    03 06                    add    (%rsi),%eax
   4:    89 07                    mov    %eax,(%rdi)
   6:    03 06                    add    (%rsi),%eax
   8:    89 07                    mov    %eax,(%rdi)
   a:    c3                       retq

这没有利用a和b不能别名的保证。

这是因为当前Rust编译器仍在开发中,还没有结合别名分析来进行优化吗?

这是因为即使在安全的Rust中,a和b仍然有可能别名吗?


Rust最初确实启用了LLVM的noalias属性,但这导致了错误编译代码。当所有支持的LLVM版本不再错误编译代码时,它将被重新启用。

如果你在编译器选项中添加-Zmutable-noalias=yes,你会得到预期的程序集:

adds:
        mov     eax, dword ptr [rsi]
        add     eax, eax
        add     dword ptr [rdi], eax
        ret

简单地说,Rust到处都是C的restrict关键字,比任何普通的C程序都流行得多。这使LLVM的极端情况超出了它能够正确处理的范围。事实证明,C和c++程序员不像Rust中使用&mut那样频繁地使用restrict。

这种情况已经发生了很多次。

Rust 1.0到1.7 -启用noalias Rust 1.8到1.27 - noalias被禁用 Rust 1.28到1.29 -启用noalias Rust 1.30到1.54 - noalias被禁用 Rust 1.54通过??- noalias根据编译器使用的LLVM版本有条件地启用

相关Rust问题

Current case Incorrect code generation for nalgebra's Matrix::swap_rows() #54462 Re-enable noalias annotations by default once LLVM no longer miscompiles them #54878 Enable mutable noalias for LLVM >= 12 #82834 Regression: Miscompilation due to bug in "mutable noalias" logic #84958 Previous case Workaround LLVM optimizer bug by not marking &mut pointers as noalias #31545 Mark &mut pointers as noalias once LLVM no longer miscompiles them #31681 Other make use of LLVM's scoped noalias metadata #16515 Missed optimization: references from pointers aren't treated as noalias #38941 noalias is not enough #53105 mutable noalias: re-enable permanently, only for panic=abort, or stabilize flag? #45029