LLVMバグレポートの提出方法

はじめに - バグを発見しましたか?

LLVMを使用しているときにバグに遭遇した場合、ぜひお知らせください。このドキュメントでは、バグを迅速に修正される可能性を高めるためにできることについて説明します。

🔒 バグがセキュリティに関連していると思われる場合は、セキュリティ問題の報告方法に従ってください。🔒

基本的には、少なくとも2つのことを行う必要があります。まず、バグがコンパイラをクラッシュさせるのか、それともコンパイラがプログラムを誤コンパイルしているのか(つまり、コンパイラが正常に実行可能ファイルを生成するが、正しく実行されない)を判断します。バグの種類に基づいて、リンクされたセクションの手順に従ってバグを絞り込み、修正する人がより簡単に見つけられるようにします。

テストケースを縮小したら、LLVMバグ追跡システムにアクセスし、必要な詳細をフォームに入力します(ラベルを選択する必要はありません。不明な場合はそのまま使用してください)。バグの説明には、次の情報を含める必要があります。

  • 問題を再現するために必要なすべての情報。

  • バグをトリガーする縮小されたテストケース。

  • LLVMを入手した場所(Gitリポジトリから入手した場合を除く)。

LLVMをより良くするためにご協力いただきありがとうございます!

クラッシュするバグ

多くの場合、コンパイラのバグは、何らかのアサーションエラーが原因でクラッシュを引き起こします。最も重要なのは、クラッシュがClangフロントエンドで発生しているのか、それともLLVMライブラリ(たとえば、オプティマイザーまたはコードジェネレーター)のいずれかに問題があるのかを把握することです。

どのコンポーネントがクラッシュしているか(フロントエンド、ミドルエンドオプティマイザー、またはバックエンドコードジェネレーター)を特定するには、クラッシュが発生したときと同じようにclangコマンドラインを実行しますが、次の追加コマンドラインオプションを追加します。

  • -emit-llvm -Xclang -disable-llvm-passes: これらのオプション(オプティマイザーとコードジェネレーターを無効にする)を渡してもclangがクラッシュする場合は、クラッシュはフロントエンドで発生しています。フロントエンドのバグにジャンプします。

  • -emit-llvm: このオプション(コードジェネレーターを無効にする)でclangがクラッシュする場合は、ミドルエンドオプティマイザーのバグが見つかりました。ミドルエンドのバグにジャンプします。

  • それ以外の場合は、バックエンドコードジェネレーターのクラッシュです。コードジェネレーターのバグにジャンプします。

フロントエンドのバグ

clangのクラッシュが発生すると、コンパイラはプリプロセスされたファイルと、clangコマンドを再生するスクリプトをダンプします。たとえば、次のようなものが表示されます。

PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:
Preprocessed source(s) and associated run script(s) are located at:
clang: note: diagnostic msg: /tmp/foo-xxxxxx.c
clang: note: diagnostic msg: /tmp/foo-xxxxxx.sh

creduceツールは、問題を再現する最小限のコードにプリプロセスされたファイルを削減するのに役立ちます。開発者の作業を容易にするために、コードを削減するためにcreduceを使用することをお勧めします。clang/utils/creduce-clang-crash.pyスクリプトは、clangがダンプするファイルで使用して、コンパイラのクラッシュをチェックするテストの作成を自動化するのに役立ちます。

cviseは、creduceの代替です。

ミドルエンド最適化のバグ

オプティマイザーでバグがクラッシュする場合は、テストケースを「-emit-llvm -O1 -Xclang -disable-llvm-passes -c -o foo.bc」を渡して.bcファイルにコンパイルします。-O1は重要です。なぜなら、-O0はすべての関数にoptnone関数属性を追加し、多くのパスはoptnone関数では実行されないからです。次に、以下を実行します。

opt -O3 foo.bc -disable-output

これがクラッシュしない場合は、フロントエンドのバグの手順に従ってください。

これがクラッシュする場合は、次のbugpointコマンドでデバッグできます。

bugpoint foo.bc -O3

これを実行し、bugpointが出力する手順と縮小された.bcファイルを使用してバグを報告します。

bugpointがクラッシュを再現しない場合、llvm-reduceはLLVM IRを削減する別の方法です。クラッシュを再現するスクリプトを作成し、次を実行します。

llvm-reduce --test=path/to/script foo.bc

これにより、クラッシュを再現する縮小されたIRが生成されます。llvm-reduceはまだかなり未熟であり、クラッシュする可能性があることに注意してください。

上記のいずれも機能しない場合は、すべてのパスの前にIRをダンプするために、--print-before-all --print-module-scopeフラグを指定してoptコマンドを実行することで、クラッシュ前のIRを取得できます。これは非常に冗長であることに注意してください。

バックエンドコードジェネレーターのバグ

コードジェネレーターでclangがクラッシュするバグを見つけた場合は、「-emit-llvm -c -o foo.bc」をclangに渡して、ソースファイルを.bcファイルにコンパイルします(すでに渡しているオプションに加えて)。foo.bcを入手したら、次のいずれかのコマンドが失敗するはずです。

  1. llc foo.bc

  2. llc foo.bc -relocation-model=pic

  3. llc foo.bc -relocation-model=static

これらのいずれもクラッシュしない場合は、フロントエンドのバグの手順に従ってください。これらのいずれかがクラッシュする場合は、次のbugpointコマンドラインのいずれかでこれを削減できます(失敗した上記のコマンドに対応するものを使用してください)。

  1. bugpoint -run-llc foo.bc

  2. bugpoint -run-llc foo.bc --tool-args -relocation-model=pic

  3. bugpoint -run-llc foo.bc --tool-args -relocation-model=static

これを実行し、bugpointが出力する手順と縮小された.bcファイルを使用してバグを報告します。bugpointで問題が発生した場合は、「foo.bc」ファイルとllcがクラッシュしたオプションを送信してください。

LTOバグ

-fltoオプションを使用しているときにLLVM LTOフェーズでクラッシュが発生するバグが発生した場合は、次の手順に従って問題を診断および報告してください。

既存のコンパイルオプションに加えて、次のオプションを使用して、ソースファイルを.bc(Bitcode)ファイルにコンパイルします。

export CFLAGS="-flto -fuse-ld=lld" CXXFLAGS="-flto -fuse-ld=lld" LDFLAGS="-Wl,-plugin-opt=save-temps"

これらのオプションはLTOを有効にし、コンパイル中に生成された一時ファイルを後で分析するために保存します。

Windowsでは、リンカーとしてlld-linkを使用する必要があります。コンパイルフラグを次のように調整します。* リンカーフラグに/lldsavetempsを追加します。* コンパイラドライバからリンクする場合は、/link /lldsavetempsを追加して、そのフラグをリンカーに転送します。

指定されたフラグを使用すると、4つの中間バイトコードファイルが生成されます。

  1. a.out.0.0.preopt.bc(リンク時最適化(LTO)が適用される前)

  2. a.out.0.2.internalize.bc(初期最適化が適用された後)

  3. a.out.0.4.opt.bc(広範な最適化セットの後)

  4. a.out.0.5.precodegen.bc(LTO後、マシンコードに変換する前)

次のコマンドのいずれかを実行して、問題の原因を特定します。

  1. opt "-passes=lto<O3>" a.out.0.2.internalize.bc

  2. llc a.out.0.5.precodegen.bc

これらのいずれかがクラッシュする場合は、llvm-reduceコマンドラインでこれを削減できます(失敗した上記のコマンドに対応するbcファイルを使用してください)。

llvm-reduce --test reduce.sh a.out.0.2.internalize.bc

reduce.shスクリプトの例

$ cat reduce.sh
#!/bin/bash -e

path/to/not --crash path/to/opt "-passes=lto<O3>" $1 -o temp.bc  2> err.log
grep -q "It->second == &Insn" err.log

ここでは、失敗したアサートメッセージをgrepしました。

これを実行し、llvm-reduceが出力する手順と縮小された.bcファイルを使用してバグを報告します。

誤コンパイル

clang が正常に実行可能ファイルを生成しても、その実行可能ファイルが正しく動作しない場合、それはコードのバグか、コンパイラのバグのどちらかです。最初に確認すべきことは、未定義の動作(例えば、定義される前に変数を読み取るなど)を使用していないことを確認することです。特に、さまざまなサニタイザー(例えば、clang -fsanitize=undefined,address)とvalgrindの下でプログラムがクリーンであるかを確認してください。私たちが追跡してきた多くの「LLVMバグ」は、最終的にLLVMではなく、コンパイルされるプログラムのバグであることが判明しました。

プログラム自体にバグがないと判断したら、プログラムをコンパイルする際に使用するコードジェネレーター(例えば、LLCやJIT)と、オプションで実行するLLVMパスのリストを選択する必要があります。例を挙げます。

bugpoint -run-llc [... optzn passes ...] file-to-test.bc --args -- [program arguments]

bugpoint は、エラーを引き起こす単一のパスにパスのリストを絞り込み、支援のためにビットコードファイルを可能な限り単純化しようとします。結果として生じるエラーを再現する方法を知らせるメッセージを出力します。

OptBisect ページでは、不正な最適化パスを見つけるための別の方法を示しています。

不正なコード生成

不正なパスによるコンパイルのデバッグと同様に、bugpointを使用して、LLCまたはJITによる不正なコード生成をデバッグできます。この場合、bugpointが実行するプロセスは、いずれかの方法で誤ってコンパイルされる関数にコードを絞り込むことを試みます。ただし、正確性を確保するためにはプログラム全体を実行する必要があるため、bugpointは影響を受けないと思われるコードをCバックエンドでコンパイルし、生成された共有オブジェクトにリンクします。

JITをデバッグするには、次のように実行します。

bugpoint -run-jit -output=[correct output file] [bitcode file]  \
         --tool-args -- [arguments to pass to lli]              \
         --args -- [program arguments]

同様に、LLCをデバッグするには、次のように実行します。

bugpoint -run-llc -output=[correct output file] [bitcode file]  \
         --tool-args -- [arguments to pass to llc]              \
         --args -- [program arguments]

特記事項: llvm/test階層にすでに存在するMultiSourceまたはSPECテストをデバッグしている場合、Makefileに指定されたプログラムオプションを渡す、事前に記述されたMakefileターゲットを使用して、JIT、LLC、CBEをデバッグする簡単な方法があります。

cd llvm/test/../../program
make bugpoint-jit

bugpointの実行が成功すると、2つのビットコードファイルが表示されます。Cバックエンドでコンパイルできるsafeファイルと、LLCまたはJITが誤ってコード生成し、エラーを引き起こすtestファイルです。

bugpointが見つけたエラーを再現するには、次のようにすれば十分です。

  1. safeビットコードファイルから共有オブジェクトを再生成します。

    llc -march=c safe.bc -o safe.c
    gcc -shared safe.c -o safe.so
    
  2. LLCをデバッグする場合は、testビットコードをネイティブにコンパイルし、共有オブジェクトとリンクします。

    llc test.bc -o test.s
    gcc test.s safe.so -o test.llc
    ./test.llc [program options]
    
  3. JITをデバッグする場合は、共有オブジェクトをロードし、testビットコードを提供します。

    lli -load=safe.so test.bc [program options]