1

clang -O2(またはオンラインデモ)を使用して次のコードスニペットをコンパイルした後:

#include <stdio.h>
#include <stdlib.h>

int flop(int x);
int flip(int x) {
  if (x == 0) return 1;
  return (x+1)*flop(x-1);
}
int flop(int x) {
  if (x == 0) return 1;
  return (x+0)*flip(x-1);
}

int main(int argc, char **argv) {
  printf("%d\n", flip(atoi(argv[1])));
}

llvmアセンブリの次のスニペットを次の場所で取得していflipます:

bb1.i:                                            ; preds = %bb1
  %4 = add nsw i32 %x, -2                         ; <i32> [#uses=1]
  %5 = tail call i32 @flip(i32 %4) nounwind       ; <i32> [#uses=1]
  %6 = mul nsw i32 %5, %2                         ; <i32> [#uses=1]
  br label %flop.exit

これは、現在のスタックをドロップすることを意味すると思いましたtail call(つまり、リターンは上位フレームに戻るので、次の命令はret %5)である必要がありますが、このコードによれば、それはそれを実行mulします。そして、ネイティブアセンブリではcall、テールの最適化なしで単純です(llcに適切なフラグがある場合でも)

誰かがclangがそのようなコードを生成する理由を説明できますか?

同様に、llvmが、nextがprevの結果を使用し、後で適切な最適化を実行するか、末尾呼び出し命令と同等のネイティブを生成するtail callことを単純にチェックできる場合、なぜllvmが持つのか理解できません。retcall

4

1 に答える 1

3

LLVMアセンブリ言語リファレンスマニュアルの「call」命令を見てください。それは言う:

オプションの「テール」マーカーは、呼び出し先関数が呼び出し元のアロカまたは変数にアクセスしないことを示します。ret命令の前に呼び出しが発生しなくても、呼び出しは「テール」とマークされる場合があることに注意してください。

ClangのLLVM最適化パスの1つが、呼び出し先が呼び出し元のアロカまたはvarargsにアクセスするかどうかを分析する可能性があります。そうでない場合、パスは呼び出しを末尾呼び出しとしてマークし、LLVMの別の部分が「末尾」マーカーをどう処理するかを判断できるようにします。たぶん、関数は今のところ本当の末尾呼び出しではありえませんが、さらに変換した後はそうなる可能性があります。パスの順序の重要性を低くするために、このように行われていると思います。

于 2010-05-25T13:37:59.917 に答える