VFP関連のメモ

とりあえずVFPを使う場合の最小のスタートアップコード。 _stack_initは適当にリンカスクリプトにでも書く事にする。とりあえずSVC modeのスタックだけ初期化しておけばいいだろう。
OSの場合は、普通VFP命令を実行して、Undefined Instructionが起こってから例外ハンドラで有効にするんだろうけど。使わないのにいちいちVFPレジスタの退避と復帰をしてたら効率悪いもんな。CPUの初期化部分を大分省いてるけど、そんなのはデバッガがやってくれるから関係ない。


.arm
.text
.globl _start
_start:
/* Initialize Stack */
mov r0, #0x13+0xc0
msr cpsr, r0
ldr sp, stack_init
bic sp, sp, #3

/* Full access to VFP */
mrc p15, 0, r0, c1, c0, 2
orr r0, r0, #(0xf << 20)
mcr p15, 0, r0, c1, c0, 2

/* Enable VFP function */
mov r0, #0x40000000
fmxr fpexc, r0
b main

stack_init:
.long _stack_init

マルチコア

カーネル空間はデマンドページングじゃないのに、あるタイミングでアクセスできなければならない空間をリードしにいくとアボートする。MMUで変換をかけてみたら、変な物理アドレスマッピングされている。しかもそのアドレスはMMUの変換テーブルがあるところだし、意味がわからない。論物変換できると見せかけて、実はページフォールトしてんのかなあ。一応対策はしてるはずなんだが。

とりあえず変なところでアボートしたら、フォールトと見なすようにするか。

LTOもどき

そういやGCCでは、4.1ぐらいから-fwhole-programオプションを付けると、複数のソースファイルを1つと見なす事が出来るためIPOが実行できる。なんかインチキくさいぞ。ちなみにこの辺の最適化はcgraphunit.cに書かれている

当然こんなのは無理

 gcc -c -fwhole-program a.c
 gcc -c -fwhole-program b.c
 gcc a.o b.o -o out

こうしないといけない

 gcc -fwhole-program a.c b.c -o out

LTO

GCC summitを見学しにいったときに、CodeSourceryのMark Mitchellと話をする機会があった。彼はGCCC++フロントエンドのメンテナで、リリースマネージャーでもある。リンク時最適化については、組み込みの世界でも非常に有用だし、待ち望んでる人達もいるので頑張って下さいと言ってきた。今考えてみれば、お前もコミットしろよという感じであるが、言語は日本語しかわからない俺にとってはハードルが高いのである。C言語とか何をやってんのかさっぱりわからん。でもソース見てたら何か出来そうな気がしてきた。気がするだけ。

GCCのltoが結構活発に開発が進められている。whopr(Whole Program Optimization)やgimple tuplesなんかとも連動して、最近はGoogleの人達がよくコードをコミットしている。CodeSourceryはどうしたんだ? SourceryG++のサポートCPUが増えたし、4.3ベースのリリースが待ってたりするから忙しいのかな?

それはともかく、-fwpa -fltrans とかいうオプションが増えた。基本的には前からあった-flto1発でいけるみたいだが、他のオプションは何が違うのか気になるところだ(良くわかってない)。メーリングリストでは、ユーザが混乱するからいっぱい似たようなオプション作るのはやめようぜという意見が出てきたので、最終的には-fltoと-fwpaぐらいになるのかもしれない。

Link-Time Optimizationといってはいるが、別にリンカで最適化してくれるわけではなく(GCCのブランチなんだから当たり前だけど...)、コンパイル過程で作られるコールグラフの情報をオブジェクト単位で残しておき、リンカが呼ばれる直前にもう1度GCCを起動して、全てのオブジェクトを入力する。そして、ローカルなコールグラフを全部ロードしてグローバルなコールグラフを作ればIPOが出来るじゃん、という寸法である。実際、2008/10/3の時点では、各オブジェクト毎に以下のようなセクションが増えてた。

 [ 4] .gnu.lto_internal  PROGBITS
 [ 5] .gnu.lto_main      PROGBITS
 [ 6] .gnu.lto_.static   PROGBITS
 [ 7] .gnu.lto_.cgraph   PROGBITS
 [ 8] .gnu.lto_.decls    PROGBITS
 [ 9] .gnu.lto_.symtab   PROGBITS

ltoのコンパイラドライバ(?)のlto.cを見たが、確かにリンカが起動する直前にGCCを起動してたし、グローバルなコールグラフを作成してから、IPAのパス(今のところインライン展開だけ)が実行されてた。ソースのコメントに書いてあったが、まだ色々問題があるようだ。

ltoプロジェクトを進めている人達は、メモリ効率とコンパイル時間を非常に気にしていて、特にコンパイル時間は倍近くなる可能性もあるため、結構悩んでいるようである。ただでさえGCCコンパイル遅いのに、更に遅くなったら誰が使うんじゃいとか言ってた気がするが、たぶん俺なら喜んで使うと思う。

どうでもいいけど、llvmのbcにはコールグラフの情報が含まれてるんでしょうね。ARMのコンパイラはそんなのが入ってるようなセクションあったかなあ。

10/16追記: -fwpaオプションはさっくりと無くなって -fwhoprに変わった。

デバッガ

個人的には、ソフトの開発にはデバッガが必須であると考えている。しかし、プロのエンジニアの人たちに聞いてみても、デバッガを使っていない人が驚くほど多い。ちゃんとした(?)ソフトウェアの開発現場に居たことがないのでよくわからないが、みんなどうやって開発してるんだろうか。

バグをほとんど出さないような天才とか、頭の中にCPUエミュレータを搭載していてステップ実行なんかが出来てしまうような特異能力者ならまだしも、普通にコードを書いて、その後のバグ取りに時間をかけている開発者にとって、デバッガは無くてはならないものだと思う。現場でほとんど使われていないような高級言語とか、最近流行のLightWeightなものに関しては知りません。

確かにprintfを使って、任意の時点での環境をそれなりに把握することができるし、これを繰り返せば原因は特定できる。熟練したソフトウェア開発者であれば、経験からバグの原因を短い時間で特定することができるかもしれない(熟練した開発者じゃないのでわからないけど)。しかし、デバッガを使えば、それほどの熟練者でなくとも、驚くほど短い時間で問題を見つける事が可能である。正しく解決できるかどうかは別だが。

デバッグの準備

基本的にはコンパイル時にデバッグ情報を付加すれば、ソースレベルでのデバッグが可能である。ただし、最適化をはずさないと非常にデバッグしづらい。デバッガの歴史は最適化との戦いの歴史である(そうでもない)。デバッグ情報も進化する余地が十分残っているのだが、変数が削除されてしまったり、命令の順序がガラッと変わったりしてしまうと、もうどうしようもない。

というわけで、この際思い切ってGCCの場合には-O0オプションで最適化をはずす事にする。デバッグ情報を付加するオプションは -g -gstabs -gstabs+など。-g はDWARFで、-gstabs -gstabs+ はstabのデバッグ情報フォーマットである。DWARF2の場合は、同じヘッダをいっぱいインクルードすると、その数だけデバッグ情報が出てしまうのでサイズがでかくなってしまう。stabは大丈夫。DWARF3でも、この辺は改良されるみたい。

最適化について

最適化をはずさないとデバッグしづらいとは書いたが、ソースレベルデバッグできないわけではない。ただ、ソース上でステップ実行したりすると、あっちこっちに飛んでいってしまって、正確にどこを実行しているのかを把握するのは難しい。ローカル変数なんかも、無くなってしまったものは当然ながら表示されない。

gdbでは、optimized outとか表示されたような気がする。gccはvar-trackingとかで、最適化されてもローカル変数が今どこにあるのかという情報を出来るだけ吐こうとするが、完全に正確というわけではない。gccのプロジェクト内では、現在もデバッグ情報の精度向上について頑張っている人もいるが、最適化のパスを書いてる人達はあまり考えてないような気がする。最適化が進めば進むほど、ソースコードと最終的なコードの対応がつかなくなり、最適化したプログラムのソースレベルデバッグが更に困難になっていくのは避けられない。

んじゃあんまりデバッグに影響を与えないような最適化のパスだけ通せばいいんじゃねーのって事で、-Ogオプションなんかも提案されているが、動きがあるんだか無いんだかわからない。

プログラムのロード

ビルドして出来たプログラムをデバッガでロードする。たいていの場合は、デバッガのロードコマンドを使えば良いだけだが、例えばJTAG-ICE経由でターゲットのメモリに転送する場合などは、メモリアドレスを指定しなければいけないかもしれない。デバッグ情報にロードするべき各セクションのアドレスが書いてあるので、多分大丈夫だろうけど、MMUが無効の場合にLinuxカーネルを転送する時なんかは(普通はそうだろう)、物理アドレスでちゃんと指定してやる必要がある。デバッグ情報に書いてあるアドレスは論理アドレスだし。

続きはいつかかく