玩式草子─ソフトウェアとたわむれる日々

第51回 X,MesaそしてLLVM[その2]

この記事を読むのに必要な時間:およそ 6 分

ふと気づくと朝晩はずいぶん冷えこむようになりました。ここ8年ぐらい使っているディスプレイは,室温が低いと,電源コードを何度か挿し直すまでスイッチが入りません。今朝,今季はじめてその症状が出て,いよいよ冬が近づいてきたことを実感しました(苦笑)⁠

さて今回は,前回ビルドしたLLVMを用いてMesaLibやX Window Systemをビルドしていく段階に進みます。

MesaLibのコンパイル

前回紹介したように,64ビット用ライブラリの配置に多少苦労したものの,MesaLibに必要なLLVMを用意することができました。LLVMはコンパイラ基盤としてさまざまな機能をライブラリ・モジュールとして提供しており,今回ビルドしたLLVM-3.3では100を越えるライブラリ群が提供されています

$ ls /usr/lib64/libLLVM*
/usr/lib64/libLLVMAArch64AsmParser.so*     /usr/lib64/libLLVMMipsCodeGen.so*
/usr/lib64/libLLVMAArch64AsmPrinter.so*    /usr/lib64/libLLVMMipsDesc.so*
/usr/lib64/libLLVMAArch64CodeGen.so*       /usr/lib64/libLLVMMipsDisassembler.so*
....
/usr/lib64/libLLVMMSP430Info.so*           /usr/lib64/libLLVMipa.so*
/usr/lib64/libLLVMMipsAsmParser.so*        /usr/lib64/libLLVMipo.so*
/usr/lib64/libLLVMMipsAsmPrinter.so*

ライブラリの中にはMipsAArch64(64ビット版ARM)⁠X86といったCPU名を持つものが多数あり,LLVMの対応ハードウェアの多さがうかがえます。これらのうちR600という名称を持つライブラリが,今回注目しているAMD(ATI)⁠ Radeon GPU用のコードを扱うライブラリです。

$ ls /usr/lib64/libLLVMR600*
/usr/lib64/libLLVMR600AsmPrinter.so*  /usr/lib64/libLLVMR600Desc.so*
/usr/lib64/libLLVMR600CodeGen.so*     /usr/lib64/libLLVMR600Info.so*

LLVM側の準備が整ったので,次にMesaLibのビルドにかかります。MesaLibにはconfigureスクリプトが付属していて機能設定は容易なものの,指定可能な機能の多さには戸惑います。今回は,BLFS(Beyond Linux From Scratch)の設定例を参考に,以下のような指定にしてみました。

 OPT_CONFIG='--enable-texture-float 
          --enable-gles1  --enable-gles2
          --enable-openvg --enable-osmesa --enable-xa --enable-gbm 
          --enable-gallium-egl --enable-gallium-gbm  --enable-gallium-llvm
          --enable-glx-tls  --with-llvm-shared-libs 
          --with-egl-platforms=drm,x11
          --with-gallium-drivers=nouveau,r300,r600,radeonsi,svga,swrast'

MesaLibではOpenGLが定義しているさまざまな機能を細かく指定できるためconfigureオプションは多岐にわたるものの,ほとんどは以前に使っていた設定をそのまま引きつぎ,Gallium3Dフレームワークに関する機能を新しく追加してみました。Gallium3D用にビルドしようとしているドライバのうち,nouveauがNVIDIAのGPU用,r300r600radeonsiはAMD RADEON GPUの各世代用,svgaswrastはこれら以外のGPU用に,必要な機能をソフトウェア的にエミュレートするためのドライバです。

この指定でconfigureは問題なく通ったものの,いざMesaLib-9.2.2をビルドしようとすると,コンパイル作業は途中でエラー終了してしまいました。

  CXX      gallivm/lp_bld_debug.lo
In file included from /usr/include/llvm/Pass.h:32:0,
                 from /usr/include/llvm/Target/TargetMachine.h:18,
                 from gallivm/lp_bld_debug.cpp:31:
/usr/include/llvm/Support/Compiler.h:309:39: fatal error: sanitizer/msan_interface.h: No such file or directory
compilation terminated.
make[3]: *** [gallivm/lp_bld_debug.lo] エラー 1
...

エラーを起している/usr/include/llvm/Support/Compiler.hの309行目前後はこんなコードになっていました。

308  #if defined(HAVE_SANITIZER_MSAN_INTERFACE_H)
309  # include <sanitizer/msan_interface.h>
310  #else
311  # define __msan_allocated_memory(p, size)
312  # define __msan_unpoison(p, size)
313  #endif

コード自体は,⁠HAVE_SANITIZER_MSAN_INTERFACE_Hが定義されていれば,<sanitizer/msan_interface.h>をインクルードせよ」⁠というごく単純な指示で,このインクルードファイルが見つからないためエラーになっているようです。

「インクルードファイルが見つからない」というエラーは,たいていの場合,ファイルを探しに行くべきディレクトリの指定漏れが原因で,configure時に必要なディレクトリをCFLAGSあたりで指示してやれば解決します。

そう思ってmsan_interface.hがどこにあるかを探してみましたが,意外なことに/usr/include/以下には見つかりませんでした。

$ find /usr/include -name msan_interface.h
$

あれれ,とインストール済みのパッケージリストから探してみると,このファイルはclangパッケージの一部として /usr/lib64/clang/3.3/include/sanitizer/msan_interface.h にあることがわかりました。

$ grep msan_interface.h /var/log/packages/*
/var/log/packages/clang:usr/lib64/clang/3.3/include/sanitizer/msan_interface.h

この配置から考えるに,このファイルはClangと緊密に結びつけられていて,GCCでコンパイルする際にはインクルードすべきではなさそうです。そのため,このファイルを読み込むように指定しているマクロ定義(HAVE_SANITIZER_MSAN_INTERFACE_H)の方を調べることにしました。

複数のディレクトリ階層に配置されたファイル群から,あるキーワードを持つファイルだけを見つけだすような作業には,findgrepを組み合わせると便利です。

以下の例ではfindの引数に-type fを指定して対象を一般ファイルに限り,-exec grep .. {} \;でそれらのファイルにgrepを実施して,grepで引っかかったファイルを-printで出力しています。

$ find /usr/include -type f -a -exec grep HAVE_SANITIZER_MSAN_INTERFACE_H {} \; -print
#define HAVE_SANITIZER_MSAN_INTERFACE_H 1
/usr/include/llvm/Config/llvm-config.h
#if defined(HAVE_SANITIZER_MSAN_INTERFACE_H)
/usr/include/llvm/Support/Compiler.h

この結果を見ると,/usr/include/llvm/Config/llvm-config.hでHAVE_SANITIZER_MSAN_INTERFACE_Hが1に定義されていることがわかりました。このllvm-config.hは,文字通りLLVMが利用する各種定義をまとめているファイルで,この定義は一番最後の部分にありました。

127  /* Define to 1 if you have the  header file. */
128  #define HAVE_SANITIZER_MSAN_INTERFACE_H 1
129

コメントを見る限り,この定義を外せば,先に見たCompiler.hは,#else以下にある自前の定義を使うようです。そこで,この定義をコメントアウトしてから再度コンパイルを行うと,無事MesaLib-9.2.2がビルドできました。

GCCではなくClangを使えばmsan_interface.hを利用できて,そのままでMesaLibをビルドできるのでは,と思われる方もいらっしゃるでしょう。実はそう考えて,手元でもClang/Clang++でMesaLibをビルドしてみました。その結果,Clang/Clang++でもMesaLibはエラーなくビルドできるものの,こうして作ったMesaLibを使うとOpenGLを利用するクライアント(glxinfoやKDE環境のkwinウィンドウマネージャ)が起動時にクラッシュしてしまい,正しく動作しませんでした。

このあたりは,ClangとMesaLibだけでなく,X Window SystemやQtあたりもからんでいる複雑な部分なので問題の詳しい切り分けはできていませんが,GCCとClangの間にはまだ多少の不整合が残っているような印象を持っています。

MesaLibは,OpenGLの機能を提供する各種ライブラリと共に,GPUを操作するドライバ類を提供しており,それらは/usr/lib64/dri/以下に配置されます。今回ビルドしたMesaLib-9.2.2では,以下のようなドライバが作成されました。

$ ls /usr/lib64/dri/*so
/usr/lib64/dri/i915_dri.so*           /usr/lib64/dri/r600_dri.so*
/usr/lib64/dri/i965_dri.so*           /usr/lib64/dri/radeon_dri.so*
/usr/lib64/dri/nouveau_dri.so*        /usr/lib64/dri/radeonsi_dri.so*
/usr/lib64/dri/nouveau_vieux_dri.so*  /usr/lib64/dri/swrast_dri.so*
/usr/lib64/dri/r200_dri.so*           /usr/lib64/dri/vmwgfx_dri.so*
/usr/lib64/dri/r300_dri.so*

今回注目しているr600_dri.soの依存関係を調べると,ちゃんとLLVMのライブラリをリンクしています。

$ ldd /usr/lib64/dri/r600_dri.so 
linux-vdso.so.1 (0x00007fffbfffe000)
libz.so.1 => /usr/lib64/../lib64/libz.so.1 (0x00007f3a65130000)
libexpat.so.1 => /usr/lib64/../lib64/libexpat.so.1 (0x00007f3a64f06000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f3a64cb3000)
..
libLLVMipo.so => /usr/lib64/../lib64/libLLVMipo.so (0x00007f3a64241000)
libLLVMVectorize.so => /usr/lib64/../lib64/libLLVMVectorize.so (0x00007f3a64009000)
libLLVMBitReader.so => /usr/lib64/../lib64/libLLVMBitReader.so (0x00007f3a63de4000)
libLLVMR600CodeGen.so => /usr/lib64/../lib64/libLLVMR600CodeGen.so (0x00007f3a63b57000)
libLLVMR600Desc.so => /usr/lib64/../lib64/libLLVMR600Desc.so (0x00007f3a638f7000)
libLLVMR600Info.so => /usr/lib64/../lib64/libLLVMR600Info.so (0x00007f3a636f6000)
...

他はどうなってるかと,lddした結果をgrepするようなスクリプトを流してみました。

$ for i in /usr/lib64/dri/* ;  do 
    chk=`ldd $i | grep LLVM`
    if [ "$chk.x" != ".x" ]; then     
       echo $i; 
    fi
done
/usr/lib64/dri/nouveau_dri.so
/usr/lib64/dri/r300_dri.so
/usr/lib64/dri/r600_dri.so
/usr/lib64/dri/radeonsi_dri.so
/usr/lib64/dri/swrast_dri.so
/usr/lib64/dri/vmwgfx_dri.so

確かにconfigureオプションで指定したドライバはGallium3D/LLVMを使うようにビルドされており,それ以外は古いMesa3Dのドライバとしてビルドされているようです。

後で調べたところ,Intelのi915はGallium3Dベースのドライバも開発されており,configure時の--with-gallium-drivers=に'i915'を追加すればGallium3Dベースのドライバがビルドされます。一方,i965はGallium3D開発の初期のころにドライバが開発されていたものの,完成を見ないまま放棄され,現在は新たなアプローチが模索されているそうです。

著者プロフィール

こじまみつひろ

Plamo Linuxとりまとめ役。もともとは人類学的にハッカー文化を研究しようとしていたものの,いつの間にかミイラ取りがミイラになってOSSの世界にどっぷりと漬かってしまいました。最近は田舎に隠棲して半農半自営な生活をしながらソフトウェアと戯れています。

URLhttp://www.linet.gr.jp/~kojima/Plamo/index.html

コメント

コメントの記入