ハードウェアを意識したプログラミングの基礎(前編)
● コンパイラが生成するコード
SIMDのように高機能な命令セット以外にも,使える命令が異なることがあります.リスト6のコードを見てください.
1: #include <stdio.h>
2: #include <linux/types.h>
3:
4: int main()
5: {
6: char fifo[] = {0x12, 0x34, 0xAB, 0xCD};
7: __u16 val = 0;
8:
9: val = *(__u16 *)fifo;
10:
11: printf("0x%Xエn", val);
12: return 0;
13: }
筆者は以前,ARMのCPUボード「Armadillo」を使ってあるデバイスを制御する必要がありました.ArmadilloにはPC104と呼ばれるISAに似た拡張バスがあります.このバスは,16ビットと8ビットのアクセスが行えるようになっています.この拡張バスにデバイスを接続して制御しなければなりません.そこで,以前のコードを16ビット・アクセスに変更したと仮定してください.
一見正しそうに見えるコードですし,アプリケーションとしては正しく動くコードになっています.しかし,デバイスは期待通りに動いてくれませんでした.どうしても分からなかったので,オシロスコープを持ってきてバスの電気信号をのぞいてみることにしました.そのときの波形が図10です.
波形を確認すると,I/O Read(IOR)信号が2回出ていることが分かりました.その後,何度コードを読み直しても,9行目で一度しかFIFOにアクセスしていません.にもかかわらず,謎のIORは2回出ているままです.なぜでしょう?
こんな時,昔の筆者はよく「ありえない!」と騒いでいました.そこに筆者の同僚で,尊敬する先輩でもあるMさんがやってきて,「ありえないことは,ありえない」とどこかの標語のようなことを教えてくれました.アナログや高速ディジタルの世界は分かりませんが,普通のディジタルの世界では突き詰めればすべて論理的に解決できるはずです.それ以来,「ありえない!」と叫ぶ前に「ありえないことは,ありえない」と自分に言い聞かせるようになりました.
また,H先輩は「バイナリを読む」ことを教えてくれました.アプリケーション開発を行っていると,高級言語がコンピュータに命令しているような錯覚に陥ります.しかし実際にCPUに命令として伝わっているのは,マシン語なのです.さすがに01のマシン語をみるのは辛いので,生成したバイナリを逆アセンブルしてみました.リスト7が逆アセンブルしたコードです.
1: 0000850c <main>:
2: 850c: e52de004 str lr, [sp, #-4]!
3: 8510: e24dd004 sub sp, sp, #4 ; 0x4
4: 8514: e3a02004 mov r2, #4 ; 0x4
5: 8518: e59f1024 ldr r1, [pc, #36] ; 8544 <.te
xt+0x184>
6: 851c: e1a0000d mov r0, sp
7: 8520: ebffffa0 bl 83a8 <.text-0x18>
8: 8524: e5dd3000 ldrb r3, [sp]
9: 8528: e5dd1001 ldrb r1, [sp, #1]
10: 852c: e59f0014 ldr r0, [pc, #20] ; 8548 <.te
xt+0x188>
11: 8530: e1831401 orr r1, r3, r1, lsl #8
12: 8534: ebffff98 bl 839c <.text-0x24>
13: 8538: e3a00000 mov r0, #0 ; 0x0
14: 853c: e28dd004 add sp, sp, #4 ; 0x4
15: 8540: e8bd8000 ldmia sp!, {pc}
16: 8544: 00008648 andeq r8, r0, r8, asr #12
17: 8548: 00008640 andeq r8, r0, r0, asr #12
問題は8行目と9行目にありました.なんとldrbが2回,つまりバイト・アクセスを2回行っていました.16ビットでアクセスするように変更したつもりなのに,なぜか2回のバイト・アクセスになっているのでした.
なぜ,バイト・アクセスなのでしょうか? 16ビット・アクセスの命令があったはずと思い,ARMのリファレンス・マニュアルを見ていてハッと気が付きました.ARMが16ビット・アクセスの命令に対応したのは,ARMv4からだったのです.コンパイラがどのARMアーキテクチャ・バージョンをターゲットにしたのかを調べるために,以下のようなコマンドを実行してみました.
$ arm-linux-gnu-gcc -E -x c /dev/null
-dM|grep __ARM_ARCH
#define __ARM_ARCH_3__ 1
デフォルトではARMv3がターゲットのようです.そこでARMv4をターゲットにするために「-march=armv4」というオプションを付けてみました.
$ arm-linux-gnu-gcc -march=armv4 -E -x c
/dev/null -dM|grep __ARM_ARCH
#define __ARM_ARCH_4__ 1
ようやくARMv4対応になったようです.そこでこのオプションを付けてもう一度コンパイルしたところ,リスト8のようなコードになりました.
1: 0000850c <main>:
2: 850c: e52de004 str lr, [sp, #-4]!
3: 8510: e24dd004 sub sp, sp, #4 ; 0x4
4: 8514: e3a02004 mov r2, #4 ; 0x4
5: 8518: e59f101c ldr r1, [pc, #28] ; 853c <.te
xt+0x17c>
6: 851c: e1a0000d mov r0, sp
7: 8520: ebffffa0 bl 83a8 <.text-0x18>
8: 8524: e1dd10b0 ldrh r1, [sp]
9: 8528: e59f0010 ldr r0, [pc, #16] ; 8540 <.te
xt+0x180>
10: 852c: ebffff9a bl 839c <.text-0x24>
11: 8530: e3a00000 mov r0, #0 ; 0x0
12: 8534: e28dd004 add sp, sp, #4 ; 0x4
13: 8538: e8bd8000 ldmia sp!, {pc}
14: 853c: 00008640 andeq r8, r0, r0, asr #12
15: 8540: 00008638 andeq r8, r0, r8, lsr r6
8行目で2回のldrbの代わりに16ビット・アクセス命令のldrhが使われていることが確認できます.オシロスコープによる計測でもIOR信号は一度になっていました(図11).やはり,「ありえないことは,ありえない」のです.