アンティーク・アセンブラ~Antique Assembler

第4回 "case" の事情

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

switchの実現

それでは,いよいよアセンブラを使用してswitch処理を実現してみましょう。

アセンブラでの実装の元となる処理は,以下リスト3の実装に相当するものとします。

リスト3 C言語による元実装

switch(x){
  case 2:
    a = 1; break;
  case 5:
    a = 2; break;
  case 8:
    a = 3; break;
  default:
    a = 4; break;
}

switchブロック部分の実装

まずはswitch構文のブロック部分(波カッコでくくられた部分)を実装してしまいます。

リスト4 switchブロックの実装

switch_case_2:
    movl    $1, a
    jmp     switch_eob
switch_case_5:
    movl    $2, a
    jmp     switch_eob
switch_case_8:
    movl    $3, a
    jmp     switch_eob
switch_default:
    movl    $4, a
    jmp     switch_eob
switch_eob: # end of block

    .global end_of_program
end_of_program:
    int3        # プログラム実行の一時停止
    nop

breakに相当する処理が,switch構文末尾位置への無条件分岐命令(JMP)を使用している以外は,C言語での記述からの単純な変換で済んでいることがわかります。

最適化の観点からは,"switch_default"末尾の"jmp switch_eob"は冗長ですが,ここでは元実装を忠実にアセンブラに変換しています。

制御遷移先配列の作成

次は「値に対応する制御遷移先の配列」を作成します。

何やら堅苦しい表現をしてはいますが,特に難しく考える必要はありません。

リスト5 制御遷移先配列の作成

addr_table:
    .long   switch_case_2  # 2(MIN + 0)
    .long   switch_default # 3(MIN + 1)
    .long   switch_default # 4(MIN + 2)
    .long   switch_case_5  # 5(MIN + 3)
    .long   switch_default # 6(MIN + 4)
    .long   switch_default # 7(MIN + 5)
    .long   switch_case_8  # 8(MIN + 6)

見ての通り,アドレス情報を列挙することで,配列相当のデータ領域を作成しただけです。

元ソースのcaseで列挙されている値は,2,5,8といったように不連続なものですが,配列自体は全体を包含するサイズで作っておいて,列挙されていない数に相当する位置には,defaultに相当するアドレスを設定しているのがミソです。

対応するcase位置への制御遷移

ここまで準備ができたなら,いよいよ値に応じて制御遷移させる処理を実装しましょう。

リスト6 制御遷移の実施

    .text
    .align  4

    .global entry_point
entry_point:
    int3        # プログラム実行の一時停止

    movl    x, %eax

    # 値域判定
    cmpl    $2, %eax
    jl      switch_default  # "x < 2" なら default
    cmpl    $8, %eax
    jg      switch_default  # "8 < x" なら default

    # 制御遷移先配列による制御遷移
    leal    addr_table, %ebx
    subl    $2, eax         # 最小値の 2 を減算
    jmp     *(%ebx, %eax, 4)

switch実現の肝になるのは,あらかじめ作成しておいた制御遷移先配列を使用して,適切な位置に制御遷移する部分です。

これまでの本連載における実装例では,分岐命令の制御遷移先の記述を,単純なアドレス値指定=直接アドレッシングで行っていましたが,今回はちょっと複雑なものになっています。このアドレッシングを仔細に見てみましょう。

  • ベース値はレジスタ ebxの値(=addr_table
  • レジスタ eax(=変数 "x" - 2)によるインデックス付き
  • インデックスの倍数は4 =32ビットアドレスの格納サイズ
  • 間接アドレッシング=メモリに格納されているアドレスを使用

以上のことから,このアドレッシングによって算出される制御遷移先は,

  • addr_table + (変数 "x" - 2) * 4の位置に格納されたアドレス」

となります。

アドレッシング記述冒頭のアスタリスク("*")は,制御遷移先が同一セグメント内であることを示すものです。

Intel x86 アーキテクチャにおける「セグメント」⁠segment)に関する詳細は,別途参考書籍等を参照してください。

OSの実装のような特権モードでのプログラミング等でなければ,通常は同一セグメント内での制御遷移だと考えて構いません。

後は変数xおよびaの格納領域を確保する必要がありますので,リスト7のように記述します。

リスト7 変数領域の確保

    .data
    .align  4

    .global x
x:
    .long   0

    .global a
a:
    .long   0

最終的なサンプルソースは,これまでに例示したソースを以下の順で並べたものとなります。

  • リスト 7
  • リスト 5
  • リスト 6
  • リスト 4

それでは実際に実行してみましょう。

図1 switch実装の動作確認

(gdb) run
....
Program received signal SIGTRAP, Trace/breakpoint trap.
0x00401001 in entry_point ()
(gdb) disassemble entry_point
Dump of assembler code for function entry_point:
0x00401000 <entry_point+0>:  int3   
0x00401001 <entry_point+1>:  mov  0x402000,%eax
0x00401006 <entry_point+6>:  cmp  $0x2,%eax
0x00401009 <entry_point+9>:  jl   0x401040 <switch_default>
0x0040100b <entry_point+11>: cmp  $0x8,%eax
0x0040100e <entry_point+14>: jg   0x401040 <switch_default>
0x00401010 <entry_point+16>: lea  0x402008,%ebx
0x00401016 <entry_point+22>: sub  $0x2,%eax
0x00401019 <entry_point+25>: jmp  *(%ebx,%eax,4)
End of assembler dump.
(gdb) set var x=5
   ※ 変数 x を 5 に設定
(gdb) stepi
... "jmp  *(%ebx,%eax,4)" まで進める ...
0x00401019 in entry_point ()
(gdb) info register ebx eax
ebx            0x402008 4202504
eax            0x3      3
   ※ レジスタ値の確認
(gdb) stepi
0x00401028 in switch_case_5 ()
   ※ "case 5" 相当の位置に制御遷移
(gdb) x/1x 0x402008+3*4
0x402014 <addr_table+12>: 0x00401028
   ※ "%ebx + %eax * 4" の位置に "case 5" 相当の
       アドレスが格納されていることを確認
(gdb)

値に応じて想定した位置に制御遷移していることがわかります。

Intel x86アーキテクチャでの実行効率の点から言えば,いきなり "jmp *addr_table-8(,%eax,4)" といったアドレッシング形式を用いるのが妥当かもしれません。

しかし,必ずしも全てのCPUアーキテクチャで,このようなアドレッシングが可能とは限りませんので,今回の実装例では少々愚直なアドレッシング方法を使用しています。

著者プロフィール

藤原克則(ふじわらかつのり)

Mercurial三昧の日々が嵩じて, いつの間にやら『入門Mercurial Linux/Windows対応』を上梓。凝り性なのが災いして,年がら年中アップアップな一介の実装屋。最近は仕事の縁が元で,OpenSolarisに入れ込む毎日。