Rustコンパイラーは、2つの可変参照がエイリアスできないと想定してコードを最適化しないのはなぜですか?


301

私が知る限り、参照/ポインタのエイリアスは、最適化されたコードを生成するコンパイラの機能を妨げる可能性があります。これは、2つの参照/ポインタが実際にエイリアスである場合に、生成されたバイナリが正しく動作することを保証する必要があるためです。たとえば、次の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
   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

ここで、コード(%rdi)はケースint *aint *bエイリアスで2倍に保存します。

これらの2つのポインターがrestrictキーワードでエイリアスできないことをコンパイラーに明示的に指示すると、次のようになります。

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

次に、Clangはバイナリコードのより最適化されたバージョンを出力します。

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

Rustは(安全でないコードを除いて)2つの可変参照がエイリアスできないことを確認しているので、コンパイラーはコードのより最適化されたバージョンを出力できるはずだと思います。

以下のコードでテストし、でコンパイルする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;
}

それは生成します:

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

これは、その保証を活用していないabエイリアスすることはできません。

これは、現在のRustコンパイラがまだ開発中で、最適化を行うためのエイリアス分析をまだ組み込んでいないためですか?

そこにチャンスがまだあることからですab、エイリアスができたとしても、安全ルーストに、?



76
補足:「Rustは2つの可変参照がエイリアスできないことを(安全でないコードを除いて)確認しているため」- unsafeコードの中でさえ、可変参照のエイリアスは許可されておらず、未定義の動作を引き起こします。生のポインタにエイリアスを設定することはできますが、unsafeコードでは実際にはRustの標準ルールを無視できません。これはよくある誤解であり、指摘する価値があります。
Lukas Kalbertodt

6
私がasmを読むのが得意ではないため、例が何をしているのかを理解するのに少し時間がかかりました。他の人を助けるために、結局のところ、+=の本体の2つの操作をaddsとして再解釈できるかどうか*a = *a + *b + *bです。ポインターがエイリアスしない場合、エイリアスが可能ですb* + *b。2番目のasmリストで何になるかを確認することもできます2: 01 c0 add %eax,%eax。しかし、エイリアスを作成する場合、エイリアスを追加することはできません。これ*bは、2回目に追加するときまでに、初回(4:最初のasmリストの行に保存する値)とは異なる値が含まれるためです。
dlukes

回答:


364

錆はもともとなかった 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で使用されrestrict&mutいるほど頻繁には使用しないことがわかりました。

これは複数回発生しています

  • Rust 1.0〜1.7 — noalias有効
  • Rust 1.8〜1.27 — noalias無効
  • Rust 1.28〜1.29 — noalias有効
  • Rust 1.30から??? — noalias無効

関連する錆の問題


12
これは当然のことです。LLVMは、多言語に対応しているという広範に渡る主張にもかかわらず、C ++バックエンドとして特別に設計されており、C ++のように十分に見えないものを常に詰まらせる傾向が常にありました。
メイソンウィーラー

47
@MasonWheelerをクリックして問題のいくつかを確認するrestrictと、ClangとGCCの両方で使用され、コンパイルが間違っているCコードの例が見つかります。そのグループでC ++自体を数えない限り、「C ++十分」ではない言語に限定されません。
Shepmaster

6
@MasonWheeler:LLVMはCやC ++のルールを中心に設計されたのではなく、LLVMのルールを中心に設計されたと思います。これは通常 CまたはC ++コードに当てはまることを前提としていますが、設計から判断できることは、トリッキーなコーナーケースを処理できない静的データ依存モデルに基づいています。証明できないデータ依存関係を悲観的に想定した場合は問題ありませんが、代わりに、保持されているのと同じビットパターンでストレージを書き込み、潜在的な、ただし証明できないデータ依存関係がある操作をno-opsアクションとして扱います。読み取りと書き込み。
スーパーキャット

8
@supercat私はあなたのコメントを数回読んだことがありますが、私は困惑していることを認めます—私は彼らがこの質問または回答とどう関係しているのか分かりません。未定義の動作はここでは機能しません。これは、複数の最適化パスが相互に不十分に相互作用している「単なる」ケースです。
シェプマスター

2
@avl_sweden繰り返しますが、これは単なるバグです。ループ展開最適化ステップはnoalias、実行時にポインタを完全に考慮しませんでした(しませんか?)。入力ポインターに基づいて新しいポインターを作成しnoalias、新しいポインターがエイリアスを作成した場合でも、属性を不適切にコピーしました。
-Shepmaster
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.