假设两个可变引用都不能别名,Rust编译器为什么不优化代码?

Zhi*_* Ma 268 compiler-optimization rust llvm-codegen

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

void adds(int  *a, int *b) {
    *a += *b;
    *a += *b;
}
Run Code Online (Sandbox Code Playgroud)

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

0000000000000000 <adds>:
   0:    8b 07                    mov    (%rdi),%eax
   2:    03 06                    add    (%rsi),%eax
   4:    89 07                    mov    %eax,(%rdi)  # The first time
   6:    03 06                    add    (%rsi),%eax
   8:    89 07                    mov    %eax,(%rdi)  # The second time
   a:    c3                       retq
Run Code Online (Sandbox Code Playgroud)

下面的代码存回(%rdi)两次的情况下,int *aint *b别名。

当我们明确告诉编译器这两个指针不能使用restrict关键字别名时:

void adds(int * restrict a, int * restrict b) {
    *a += *b;
    *a += *b;
}
Run Code Online (Sandbox Code Playgroud)

然后Clang将发出二进制代码的更优化版本:

0000000000000000 <adds>:
   0:    8b 06                    mov    (%rsi),%eax
   2:    01 c0                    add    %eax,%eax
   4:    01 07                    add    %eax,(%rdi)
   6:    c3                       retq
Run Code Online (Sandbox Code Playgroud)

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

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

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

它产生:

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
Run Code Online (Sandbox Code Playgroud)

这不采取保证的优势a,并b不能别名。

这是因为当前的Rust编译器仍在开发中,并且尚未合并别名分析来进行优化吗?

这是因为即使在安全的Rust中,仍然有可能ab可能出现别名吗?

She*_*ter 323

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

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

adds:
        mov     eax, dword ptr [rsi]
        add     eax, eax
        add     dword ptr [rdi], eax
        ret
Run Code Online (Sandbox Code Playgroud)

简而言之,Rust将C的restrict关键字等价于各处,比任何普通的C程序都要普遍得多。这对LLVM的极端情况产生了超出其正确处理能力的影响。事实证明,C和C ++程序员根本不像Rust中restrict那样频繁&mut使用。

这已经发生过多次了

  • Rust 1.0至1.7-已noalias启用
  • Rust 1.8到1.27- noalias禁用
  • Rust 1.28至1.29- noalias启用
  • Rust 1.30到??? — noalias禁用

相关Rust问题

  • @MasonWheeler如果您单击某些问题,则可以找到使用`restrict'且在Clang和GCC上均未正确编译的C代码示例。除非[您将C ++本身归为该组],否则它并不限于“ C ++还不够”的语言(https://en.wikipedia.org/wiki/No_true_Scotsman)。 (37认同)
  • 这不足为奇。尽管LLVM拥有广泛的多语言友好性主张,但它是专门为C ++后端设计的,并且一直很容易在看起来不太像C ++的事物上窒息。 (9认同)
  • @supercat我已经读过几次您的评论,但是我承认我很困惑—我不知道他们与这个问题或答案有什么关系。未定义的行为在这里不起作用,这只是“多次”优化传递彼此之间不良交互的情况。 (8认同)
  • @MasonWheeler:我不认为LLVM确实是围绕C或C ++规则设计的,而是围绕LLVM规则设计的。它假设*通常*对C或C ++代码成立,但据我所知,该设计基于静态数据依赖模型,该模型不能处理棘手的极端情况。如果它悲观地假设无法证明数据依赖性,那将是可以的,但是它将其视为无操作动作,该操作将以与所持有的位模式相同的位模式写入存储,并且对存储有潜在但不可证明的数据依赖性。读和写。 (6认同)
  • @avl_sweden 重申,它[*只是一个错误*](https://bugs.llvm.org/show_bug.cgi?id=39282)。循环展开优化步骤在执行时没有完全考虑“noalias”指针。它根据输入指针创建新指针,不正确地复制“noalias”属性,即使新指针具有别名。 (5认同)
  • @avl_sweden:但是,在某些情况下,不涉及指针派生的时间。给定文件范围内的 `int a[1],b[1],c[1],*p;`,标准明确认识到 `p==b+1` 可能同时为 true `p==a` 或 `p==c`,但 clang 和 gcc 都不这样做。给定 `int temp = a[0]+c[0]; 如果 (p==b+1) *p+=1; return temp+a[0]+c[0];` clang 和 gcc 都容易生成简单返回 `temp+temp` 的代码,而不考虑它们是否紧接着放置了 `a[]` 或 `c[]` `b[]`。 (2认同)
  • @avl_sweden:这不是编译器没有注意到某些事情的情况。这是一个实现故意进行推断的情况,通常碰巧是正确的(如果在“b”后面的空格中放置除“a”或“c”之外的其他内容,则可能导致正确的推断)但不健全在一般情况下。 (2认同)