なぜLLVMは冗長変数を割り当てるのですか?


9

列挙型の定義とmain関数を含む簡単なCファイルを次に示します。

enum days {MON, TUE, WED, THU};

int main() {
    enum days d;
    d = WED;
    return 0;
}

次のLLVM IRに転送されます。

define dso_local i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  store i32 2, i32* %2, align 4
  ret i32 0
}

%2明らかにd変数であり、2が割り当てられます。%1ゼロが直接返される場合、何に対応しますか?


1
このIRを作成するためにどのフラグを使用しましたか?
1

@arrowd、最新の安定したLLVMスイートをインストールして実行しましたclang-9 -S -emit-llvm simple.c
macleginn

1
以前の初期化と関係があると思いますmaingodbolt.org/z/kEtS-s)。リンクは、アセンブリがソースにマップされる方法を示しています
Pradeep Kumar

2
@PradeepKumar:実際、関数の名前を以外mainに変更すると、謎の余分な変数が消えます。興味深いことに、returnステートメントを完全に省略すると(mainCではで合法であり、と同等ですreturn 0;)、それも消えます。
Nate Eldredge

1
@macleginn:よくわかりません。あなたが宣言する場合mainとしてint main(int argc, char **argv)、あなたが見るargcと、argvスタックにコピーされますが、神秘的なゼロの変数は、それらに加えて、まだそこにあります。
Nate Eldredge

回答:


3

この%1レジスタは、関数内の複数のreturnステートメント処理するためにclangによって生成されました。整数の階乗を計算する関数があると想像してください。このように書く代わりに

int factorial(int n){
    int result;
    if(n < 2)
      result = 1;
    else{
      result = n * factorial(n-1);
    }
    return result;
}

あなたはおそらくこれを行うでしょう

int factorial(int n){
    if(n < 2)
      return 1;
    return n * factorial(n-1);
}

どうして?Clangがresult戻り値を保持する変数を挿入するからです。わーい。それがまさにその目的です%1。irを見て、コードを少し変更したバージョンを探します。

変更されたコード、

enum days {MON, TUE, WED, THU};

int main() {
    enum days d;
    d = WED;
    if(d) return 1;
    return 0;
}

IR、

define dso_local i32 @main() #0 !dbg !15 {
    %1 = alloca i32, align 4
    %2 = alloca i32, align 4
    store i32 0, i32* %1, align 4
    store i32 2, i32* %2, align 4, !dbg !22
    %3 = load i32, i32* %2, align 4, !dbg !23
    %4 = icmp ne i32 %3, 0, !dbg !23
    br i1 %4, label %5, label %6, !dbg !25

 5:                                                ; preds = %0
   store i32 1, i32* %1, align 4, !dbg !26
   br label %7, !dbg !26

 6:                                                ; preds = %0
  store i32 0, i32* %1, align 4, !dbg !27
  br label %7, !dbg !27

 7:                                                ; preds = %6, %5
  %8 = load i32, i32* %1, align 4, !dbg !28
  ret i32 %8, !dbg !28
}

%1自分が便利になっていることがわかりますか?他の人が指摘したように、returnステートメントが1つしかない関数の場合、この変数はおそらくllvmの最適パスの1つによって削除されます。


1

なぜこれが問題になるのですか?実際の問題は何ですか?

LLVMのアーキテクチャは、かなり単純なフロントエンドと多くのパスに基づいています。フロントエンドは正しいコードを生成する必要がありますが、適切なコードである必要はありません。彼らは動作する最も簡単なことを行うことができます。

この場合、Clangは、何にも使用されないことが判明した2つの命令を生成します。LLVMの一部が余分な命令を取り除くため、これは一般的に問題ではありません。Clangはそれが起こると信じています。Clangは不要なコードの出力を回避する必要はありません。その実装は、正確さ、単純さ、テスト容易性などに焦点を当てている場合があります。


1

Clangは構文解析を使用して行われますが、LLVMは最適化を開始していません。

Clangフロントエンドは、マシンコードではなくIR(中間表現)を生成しました。これらの変数はSSA(単一の静的割り当て)です。それらはまだレジスターにバインドされておらず、実際には最適化後にバインドされていません。冗長であるためです。

そのコードは、ソースのややリテラル表現です。これは、clangが最適化のためにLLVMに渡すものです。基本的に、LLVMはそれから始まり、そこから最適化します。実際、バージョン10およびx86_64の場合、llc -O2は最終的に次のものを生成します。

main: # @main
  xor eax, eax
  ret

私はこのレベルのプロセスを理解しています。そもそもなぜこのIRが生成されたのか知りたいと思いました。
macleginn

コンパイラを単一のパスと考えているかもしれません。IRを生成するClangフロントエンドから始まるパスのパイプラインがあります。このテキストIRも生成されませんでした。代わりに、clang -emit-llvm -S file.cppで要求された誰かが実際にバイナリシリアル化可能なビットコードバージョンのIRを生成しました。LLVMは複数のパスとして構成され、それぞれがIRを取得して最適化します。最初のLLVMパスは、ClangからIRを取得します。同じオプティマイザ+コードジェネレータで別の言語をサポートするためにClangをFortran FEで置き換えることができるため、IRが必要です。
オルソニスト
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.