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

第50回X、MesaそしてLLVMその1]

秋分の日を過ぎると、日中の日射しには厳しさが残るものの、朝晩は肌寒いくらいになってきました。発熱量が大きくて暑い時期には敬遠していた開発用のマシンに電源が入ることも多くなり、Plamo-5.2へ向けたパッケージ作りの作業に励む日々が続いています。

Plamo-5.2は、今年の5月下旬にリリースしたPlamo-5.1に続くPlamo-5系統の2度目のメンテナンスリリースで、5.1では拾えなかったり、5.1以後にアップデートされたソフトウェアに対応して、年内には公開したいと考えています。

現在、Plamo-5.2用のツリーでは仮想化関係のパッケージの更新が盛んで、仮想化回りの機能はPlamo-5.2の特長のひとつになりそうです。しかし仮想化回りについてはまた別の機会にとりあげることにして、しばらくはPlamo-5.1では拾い損なったX Window Systemとその基盤のひとつとなっている3Dグラフィックス用のMesaLibさらにそれに必要となるLLVMについて紹介してみましょう。

X Window SystemとMesaLib

X Window Systemは、マサチューセッツ工科大学(MIT)の学内コンピュータ環境をMITとIBM、DECが共同で研究開発したアテナプロジェクト(Project Athena⁠⁠」の一部として生まれたウィンドウシステムです。そのような出自から、最初のX Window SystemはMITが中心となったMIT X Consortiumが、アテナプロジェクトの終了後は非営利な業界団体であるX Consortiumが引き継いで開発を続けてきました。その後、The Open GroupX.OrgXFree86といったさまざまな組織を経て、現在ではX.Org Foundationが管理主体となりつつ、誰でも自由に開発に参加できる「バザールモデル」の形式で開発が進んでいます。

図1 X.Org Foundationの公式サイト
図1 X.Org Foundationの公式サイト

このような開発スタイルの変化を反映して、X Window Systemのソースコードは、全てのソフトウェアをひとつのディレクトリ以下に収めていたモノリシック構造から、それぞれのライブラリやコマンドを独立したソフトウェアとして自由にビルド、インストールできるモジュール構造に変更されました。そのため、各ソフトウェアの開発テンポも従来に比べて自由度が増し、X Window Systemとしての公式なリリースは定期的に行われるものの、それぞれのソフトウェアはそれとは独立にバージョンアップを進めています。

その結果、Plamo-5.1で採用しているX11R77は、X Window Systemとしてはいまだに最新版ではあるものの、Xサーバや各ビデオカード用のドライバ、ライブラリなど構成しているパーツの中にはずいぶんバージョンが進んだものがあり、X11R77当時のバージョンは少々見劣りするようになってしまいました。

この問題には、Plamo-5.1をまとめていた際にも気がついていて、主要なパーツ(特にXサーバとドライバ類)だけでも新しいバージョンに追従しようかと考えましたが、その際に問題になったのが3Dグラフィックス機能を担当しているMesaLibでした。

MesaLibはOpenGLと呼ばれる3Dグラフィックスの仕様をOSSとして実装したライブラリで、X Window Systemも3Dグラフィックスの処理はMesaLibを利用しています。

図2 Mesa3D projectの公式サイト
図2 Mesa3D projectの公式サイト

元々、MesaLibはOpenGLが定義している3Dグラフィックス用の機能をソフトウェア的に実現しようとしたプロジェクトで、CPUで生成した画像情報をグラフィックスカードに送ってディスプレイに表示していました。しかし、グラフィックスカードに搭載されているGPU ⁠Graphics Processing Unit)の進化が進み、3Dグラフィックス機能の多くがハードウェア的に実現できるようになるにつれ、MesaLibも処理の多くをGPUにゆだねるように設計が変更されました。

GPUは"Graphics Processing Unit"の名称通り、コンピュータの画面処理、特に3Dグラフィックスの処理を効率よく実現できるように設計されたマイクロプロセッサです。

3Dグラフィックスでは、オブジェクトに光がどのように当たってどのような質感や陰影が生じるのかを、オブジェクトの構成単位ごとに繰り返し計算する必要があります。そのような計算はいわゆる「並列化」することで効率が改善するので、AMD(ATI)RadeonやNVIDIAのGeForceといった最近のGPUは並列処理をきわめて高速に実行できるような設計になっています。

これらのGPUはマイクロプロセッサなので、操作するためにはGPUが理解するコマンドを送ってやる必要があります。ところが、GPUはIntelやAMDのCPUとは全く異なる設計になっているため、GPUのコマンドはCPUのコマンドと根本的に異なります。そのため、MesaLibではOpenGLの機能を実現するために必要な操作を、GPUが理解するコマンドに変換した上でグラフィックスカードに送る必要があります。

グラフィックス用に特化した設計になっているため、GPUのコマンドはCPUのコマンドほど複雑ではないものの、ある言語(OpenGL)で書かれたコードをマイクロプロセッサ(GPU)が理解するコマンドに変換する、という作業は、ドライバやライブラリではなく、本来はコンパイラがするべき仕事です。

そこで最近のMesaLibでは、LLVM(Low Level Virtual Machine⁠⁠」と呼ばれるソフトウェアを利用して、GPUを操作するコマンドを生成するようにしています。つまり、X Window Systemのパーツ(Xサーバ)をビルドするためには、新しいMesaLib(Gallium3D)が必要で、新しいMesaLibをビルドするためにはLLVMを用意する必要があるわけです。

LLVM

LLVMは、元々はイリノイ大学で開発されたコンパイラ基盤(Compiler Infrastructure⁠⁠」です。⁠コンパイラ基盤」というのは聞きなれない言葉ですが、特定の言語やCPUに依存せず、さまざまなコンパイラで汎用的に使えるコンピュータ用の中間表現や最適化処理に関する機能をこのように呼んでいるようです。

図3 LLVMの公式サイト
図3 LLVMの公式サイト

最初期のコンパイラは、ある言語で記述されたソースコードを特定のCPU用のアセンブリコードに直接変換するソフトウェアでした。しかし、さまざまに提案される言語や次々に開発されるCPUそれぞれに専用のコンパイラを開発するのは大変です。

そこで各言語とCPUを直接対応づけるのではなく、言語やCPUに依存しないコンピュータ専用の中間表現を介して、両者を多対多で結びつけよう、という考え方が生まれました。このようにしておけば、新しい言語を追加するにはその言語を中間表現に変換する処理を書けばいいわけですし、新しいCPUに対応するのも中間表現からそのCPU用のコマンドを生成する処理を書くだけで済みます。最適化等の処理はそれぞれの言語やCPUごとではなく、この中間表現に対して行なうようにすれば、言語やCPUに関わらずにその恩恵を受けることができます。

このような設計の魁(さきがけ)となったのが、GNUプロジェクトが開発したGCC(GNU Compiler Collection)です。GCCでは、コンピュータ専用の中間表現としてRTL(Register Transfer Language)を採用し、CやC++、FORTRAN、あるいはJavaやAdaといった言語をいったんRTL形式に変換してから最適化処理を行い、その結果をさまざまなCPU用のアセンブリコードに変換する、という設計になっています。このような設計のおかげで、GCCは60種類を越えるCPUに対応したコンパイラとして、長くOSSのデフォルトコンパイラの地位を占めてきました。

しかしながら、最初のバージョンが1987年に公開されて以来、長年に渡って多くの開発者がさまざまな機能を追加してきたGCCは、開発やメンテナンスも簡単ではありません。

LLVMはそのような現状を踏まえて生まれたソフトウェアです。⁠低水準仮想マシン(Low Level Virtual Machine⁠⁠」の名前が示すように、LLVMの中心部分は特定のアーキテクチャに依存しない仮想的なCPUとこの仮想CPU用に生成された中間表現を最適化する機能です。これらの機能はLLVM以外の環境からも利用しやすいように、それぞれが独立したライブラリとして提供されています。

LLVMでは、元々、各言語を解析するフロントエンド部はGCCを流用することにして、GCCが生成した中間表現を磨きあげる(最適化する)ことを目的としていました。しかし最近では、CやC++、Objective-Cのソースコードを解析するためのClangと呼ばれる独自のフロントエンド部が開発され、GCCとは独立したC/C++/Objective-Cコンパイラとして利用できるようになっています。

LLVMは中間表現の最適化処理などの機能を独立した共有ライブラリとして提供しており、LLVM用のフロントエンド部を書けば、後の処理はそれら共有ライブラリを利用することが可能です。Gallium3DはLLVMのこのような特徴を利用して、自身はOpenGLの機能をLLVM用の中間表現に変換する処理を行い、その後の処理はLLVMのライブラリを利用するようになっています。

このように書いてくれば、それぞれの段階はそれなりの必然性を持っているように思われるでしょう。しかし、こういう構造になった結果、X Window Systemを更新するためにはMesaLibを新しくする必要があり、MesaLibを新しくするためにはきちんと動作するLLVMの環境を用意する必要があるわけで、それぞれをパッケージ化しなければいけないディスリビュータの立場からすると安易に手を出すことがためらわれます。そのため、Plamo-5.0から5.1へのアップデートではX Window Systemの更新を見送り、今回改めてLLVMのパッケージ化から挑戦したのでありました。

Plamo-5.1用LLVM

さて、ずいぶん前置きが長くなりましたが、X Window Systemの更新を目指して、まずは64ビット環境用にLLVMをパッケージ化する作業に着手しました。

LLVMではビルド環境構築用に、伝統的なconfigureスクリプトとより新しいcmakeの2つのツールが用意されています。当初はBLFS(Beyond Linux From Scratch)のビルド方法等を参考にして、configureスクリプトを用いたビルドスクリプトを書いてみたものの、configure時に--libdirオプションを指定しても、生成したライブラリ類が64ビット環境用のディレクトリ(/usr/lib64/)ではなく/usr/lib/にインストールされてしまいます。

cmakeには-DLLVM_LIBDIR_SUFFIXという指定があるようなのでこちらも試してみましたが、やはり共有ライブラリが/usr/lib64/には行きません。

さてどうしたものか…、と他のディストリビューションを調べてみると、LLVMのソースコードのあちこちに共有ライブラリの位置を/usr/lib/に決め打ちしているところがあり、それらを適宜修正する必要があるようです。そこで、それらの修正処理をPlamoBuildスクリプトに取り込んでみました。

  1   #!/bin/sh
  2   ##############################################################
  3   url='http://llvm.org/releases/3.3/llvm-3.3.src.tar.gz
  4    http://llvm.org/releases/3.3/cfe-3.3.src.tar.gz
  5    http://llvm.org/releases/3.3/compiler-rt-3.3.src.tar.gz'
  6   pkgbase=llvm
  7   vers=3.3
  8   arch=x86_64
  9   # arch=i586
 10   build=P1
....

153     # Fix installation directories, ./configure doesn't set them right
154       if [ "$arch" = "x86_64" ]; then
155           sed -e 's:\$(PROJ_prefix)/docs/llvm:$(PROJ_prefix)/share/doc/llvm-3.3:'  \
156               -e 's:\$(PROJ_prefix)/lib:$(PROJ_prefix)/lib64:' \
157               -i Makefile.config.in
158           sed  '/ActiveLibDir = ActivePrefix/s:lib:lib64:' -i tools/llvm-config/llvm-config.cpp
159           sed -i 's:LLVM_LIBDIR="${prefix}/lib":LLVM_LIBDIR="${prefix}/lib64":'  autoconf/configure.ac  configure
160           sed -i s,/lib/,/lib64/,g tools/clang/lib/Driver/Tools.cpp  \
161                              tools/clang/test/Preprocessor/iwithprefix.c
162           rm tools/clang/test/Driver/x86_features.c
163           rm tools/clang/test/Driver/linux-ld.c
164       fi
165    # Fix insecure rpath (http://bugs.archlinux.org/task/14017)
166       sed -i 's:$(RPATH) -Wl,$(\(ToolDir\|LibDir\|ExmplDir\))::g' Makefile.rules
167   
....

186       cmake -G "Unix Makefiles" \
187           -DBUILD_SHARED_LIBS=ON \
188           -DCMAKE_BUILD_TYPE=Release \
189           -DCMAKE_INSTALL_PREFIX=/usr \
190           -DLLVM_LIBDIR_SUFFIX=${suffix} \
191           -DCLANG_RESOURCE_DIR=../${libdir}/clang/3.3 \
192           -DLLVM_REQUIRES_RTTI=ON \
193           -DLLVM_ENABLE_TIMESTAMPS=OFF \
194           -DLLVM_ENABLE_ASSERTIONS=OFF \
195           -DLLVM_ENABLE_PIC=ON \
196           -DLLVM_BINUTILS_INCDIR=/usr/include \
197           -DLLVM_TARGETS_TO_BUILD=all \
198           -DLLVM_HOST_TRIPLE=$HOST_TRIPLE \
199           -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=R600 \
200           ..

単純を旨とするPlamoBuildスクリプトとしては、ずいぶん面倒な処理が必要となりましたが、このような試行錯誤の結果、何とか64ビット版のLLVMとC/C++用フロントエンドであるClangを動かすことができるようになりました。


$ clang --help
OVERVIEW: clang LLVM compiler

USAGE: clang-3 [options] <inputs>

OPTIONS:
  -###                    Print the commands to run for this compilation
  -E                      Only run the preprocessor
  -F <value>              Add directory to framework include search path
  -H                      Show header includes and nesting depth
  -I <value>              Add directory to include search path
...

これらを使ってC++のサンプルコードをコンパイルしてみました。


$ clang++ -v sample.c++ 
clang version 3.3 (tags/RELEASE_33/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
 "/usr/bin/clang-3.3" -cc1 -triple x86_64-pc-linux-gnu -emit-obj -mrelax-all 
      -disable-free -disable-llvm-verifier -main-file-name sample.c++ -mrelocation-model static 
      -mdisable-fp-elim -fmath-errno -masm-verbose -mconstructor-aliases -munwind-tables 
 ....
#include <...> search starts here:
 /usr/lib64/gcc/x86_64-pc-linux-gnu/4.7.3/../../../../include/c++/4.7.3
 /usr/lib64/gcc/x86_64-pc-linux-gnu/4.7.3/../../../../include/c++/4.7.3/x86_64-pc-linux-gnu
 /usr/lib64/gcc/x86_64-pc-linux-gnu/4.7.3/../../../../include/c++/4.7.3/backward
 /usr/local/include
 /usr/bin/../lib64/clang/3.3/include
 /usr/include
End of search list.
 "/usr/bin/ld" --eh-frame-hdr -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o a.out 
    /usr/lib64/gcc/x86_64-pc-linux-gnu/4.7.3/../../../../lib64/crt1.o 
    /usr/lib64/gcc/x86_64-pc-linux-gnu/4.7.3/../../../../lib64/crti.o 
    /usr/lib64/gcc/x86_64-pc-linux-gnu/4.7.3/crtbegin.o -L/usr/lib64/gcc/x86_64-pc-linux-gnu/4.7.3 
...
$ ./a.out 
Hello World by C++ !

さて、LLVMの準備が整ったので、いよいよ新しいMesaLibのビルドにとりかかれるわけですが、すいぶん長くなってしまったので、続きは次回に譲りましょう。

おすすめ記事

記事・ニュース一覧