■ FizzBuzz x86 for バイナリアン
昨日の続き。今日は息抜きに FizzBuzz.com (MS-DOS 16bit版) を作ってみました。
0000000 b4 02 bb 31 30 30 ed e8 2c 00 e8 29 00 e8 39 00 0000020 e8 23 00 e8 3e 00 e8 30 00 e8 1a 00 e8 17 00 e8 0000040 27 00 e8 2f 00 e8 0e 00 e8 1e 00 e8 08 00 e8 05 0000060 00 e8 13 00 eb d1 80 ff 30 74 04 88 fa cd 21 88 0000100 da cd 21 e8 28 00 c3 fe c5 b2 46 cd 21 b2 69 cd 0000120 21 e9 08 00 b2 42 cd 21 b2 75 cd 21 b2 7a cd 21 0000140 cd 21 84 ed 75 04 e8 05 00 c3 30 ed eb e6 b2 0d 0000160 cd 21 b2 0a cd 21 fe c3 80 fb 3a 75 09 b3 30 fe 0000200 c7 80 ff 3a 74 01 c3 b8 00 4c cd 21 0000214
これで140byte
DISアセンブルするとこんな感じです。
00000000 B402 mov ah,0x2 00000002 BB3130 mov bx,0x3031 00000005 30ED xor ch,ch 00000007 E82C00 call 0x36 0000000A E82900 call 0x36 0000000D E83900 call 0x49 00000010 E82300 call 0x36 00000013 E83E00 call 0x54 00000016 E83000 call 0x49 00000019 E81A00 call 0x36 0000001C E81700 call 0x36 0000001F E82700 call 0x49 00000022 E82F00 call 0x54 00000025 E80E00 call 0x36 00000028 E81E00 call 0x49 0000002B E80800 call 0x36 0000002E E80500 call 0x36 00000031 E81300 call 0x47 00000034 EBD1 jmp short 0x7 00000036 80FF30 cmp bh,0x30 00000039 7404 jz 0x3f 0000003B 88FA mov dl,bh 0000003D CD21 int 0x21 0000003F 88DA mov dl,bl 00000041 CD21 int 0x21 00000043 E82800 call 0x6e 00000046 C3 ret 00000047 FEC5 inc ch 00000049 B246 mov dl,0x46 0000004B CD21 int 0x21 0000004D B269 mov dl,0x69 0000004F CD21 int 0x21 00000051 E90800 jmp 0x5c 00000054 B242 mov dl,0x42 00000056 CD21 int 0x21 00000058 B275 mov dl,0x75 0000005A CD21 int 0x21 0000005C B27A mov dl,0x7a 0000005E CD21 int 0x21 00000060 CD21 int 0x21 00000062 84ED test ch,ch 00000064 7504 jnz 0x6a 00000066 E80500 call 0x6e 00000069 C3 ret 0000006A 30ED xor ch,ch 0000006C EBE6 jmp short 0x54 0000006E B20D mov dl,0xd 00000070 CD21 int 0x21 00000072 B20A mov dl,0xa 00000074 CD21 int 0x21 00000076 FEC3 inc bl 00000078 80FB3A cmp bl,0x3a 0000007B 7509 jnz 0x86 0000007D B330 mov bl,0x30 0000007F FEC7 inc bh 00000081 80FF3A cmp bh,0x3a 00000084 7401 jz 0x87 00000086 C3 ret 00000087 B8004C mov ax,0x4c00 0000008A CD21 int 0x21
もっと短くできるかも。今日はここまで。
2007.05.12 追記
x86のオペコード・マップ(ニーモニック表)を見ると、まだ最適化できる余地が残っていることがわかります。
dias16.lzh に含まれる 80X86_OP.TXT
http://www.vector.co.jp/soft/dos/prog/se008548.html
IA-32 インテル(R) アーキテクチャ・ソフトウェア・デべロッパーズ・マニュアル
中巻:命令セット・リファレンス (日本語 PDF ファイル: 9,139KB)
ftp://download.intel.co.jp/jp/developer/jpdoc/24547103_j.pdf
- 第3章 命令セット・リファレンス (p.43-811)
- 付録A オペコード・マップ (p.815-834)
- 付録B 命令フォーマットおよびエンコーディング (p.837-888)
例えば、8bitのレジスタをINCするよりも16bitのワードレジスタをINCしたほうが実はオプコードが1byte少なかったりします。
inc ch ; FEC5 inc cl ; FEC1 inc cx ; 41
chレジスタの領域までオーバーフローしないときなら、inc cl の代わりに inc cx を使うと1byte節約することができます。
第一段階の最適化
inc *x / dec *x 命令をできるだけ使うようにレジスタを再配置して、call命令の呼び出し回数を減らし、ジャンプ命令を全部相対指定にしたら 140 → 118 byte (-32byte) に短縮できました。
あと、出力する区切り文字も CR LF(2byte) から半角スペース(1byte)に変更しました。
00000000 B402 mov ah,0x2 00000002 BB003A mov bx,0x3a00 00000005 B93130 mov cx,0x3031 00000008 E82000 call 0x2b 0000000B E83000 call 0x3e 0000000E E81D00 call 0x2e 00000011 E83400 call 0x48 00000014 E81100 call 0x28 00000017 E82400 call 0x3e 0000001A E82B00 call 0x48 0000001D E80E00 call 0x2e 00000020 E80500 call 0x28 00000023 E81700 call 0x3d 00000026 EBE0 jmp short 0x8 00000028 E81300 call 0x3e 0000002B E80000 call 0x2e 0000002E 80FD30 cmp ch,0x30 00000031 7404 jz 0x37 00000033 88EA mov dl,ch 00000035 CD21 int 0x21 00000037 88CA mov dl,cl 00000039 CD21 int 0x21 0000003B EB22 jmp short 0x5f 0000003D 43 inc bx 0000003E B246 mov dl,0x46 00000040 CD21 int 0x21 00000042 B269 mov dl,0x69 00000044 CD21 int 0x21 00000046 EB08 jmp short 0x50 00000048 B242 mov dl,0x42 0000004A CD21 int 0x21 0000004C B275 mov dl,0x75 0000004E CD21 int 0x21 00000050 B27A mov dl,0x7a 00000052 CD21 int 0x21 00000054 CD21 int 0x21 00000056 84DB test bl,bl 00000058 7502 jnz 0x5c 0000005A EB03 jmp short 0x5f 0000005C 4B dec bx 0000005D EBE9 jmp short 0x48 0000005F B220 mov dl,0x20 00000061 CD21 int 0x21 00000063 41 inc cx 00000064 38F9 cmp cl,bh 00000066 7508 jnz 0x70 00000068 B130 mov cl,0x30 0000006A FEC5 inc ch 0000006C 38FD cmp ch,bh 0000006E 7401 jz 0x71 00000070 C3 ret 00000071 B8004C mov ax,0x4c00 00000074 CD21 int 0x21
COM形式の実行ファイルをMS-DOSモードで実行した結果は以下の通りです。
コードの解説はあとで書く...かもしれない。
2007.05.12 追記
Yappoさんがコードにコメントをつけてくれました → http://subtech.g.hatena.ne.jp/yappo/20070511/1178907299
2007.05.14 追記
LOOPZ(=LOOPE)命令を活用することにより88byteに短縮できました。
$ od -tx1 -Ax f88.com 000000 b9 05 00 be 03 00 bb 31 30 b4 02 4e 74 10 e2 23 000010 b1 05 b2 42 cd 21 b2 75 cd 21 84 ed eb 0b be 03 000020 00 b2 46 cd 21 b2 69 cd 21 b2 7a cd 21 cd 21 e1 000030 0f eb dd 80 ff 30 74 04 88 fa cd 21 88 da cd 21 000040 b2 20 cd 21 43 80 fb 3a 75 c1 b3 30 fe c7 80 ff 000050 3a 75 b8 b8 00 4c cd 21 000058
ndisasm(w) f88.com でDISアセンブルするとこんな感じです。
00000000 B90500 mov cx,0x5 00000003 BE0300 mov si,0x3 00000006 BB3130 mov bx,0x3031 00000009 B402 mov ah,0x2 0000000B 4E dec si 0000000C 7410 jz 0x1e 0000000E E223 loop 0x33 00000010 B105 mov cl,0x5 00000012 B242 mov dl,0x42 00000014 CD21 int 0x21 00000016 B275 mov dl,0x75 00000018 CD21 int 0x21 0000001A 84ED test ch,ch 0000001C EB0B jmp short 0x29 0000001E BE0300 mov si,0x3 00000021 B246 mov dl,0x46 00000023 CD21 int 0x21 00000025 B269 mov dl,0x69 00000027 CD21 int 0x21 00000029 B27A mov dl,0x7a 0000002B CD21 int 0x21 0000002D CD21 int 0x21 0000002F E10F loope 0x40 00000031 EBDD jmp short 0x10 00000033 80FF30 cmp bh,0x30 00000036 7404 jz 0x3c 00000038 88FA mov dl,bh 0000003A CD21 int 0x21 0000003C 88DA mov dl,bl 0000003E CD21 int 0x21 00000040 B220 mov dl,0x20 00000042 CD21 int 0x21 00000044 43 inc bx 00000045 80FB3A cmp bl,0x3a 00000048 75C1 jnz 0xb 0000004A B330 mov bl,0x30 0000004C FEC7 inc bh 0000004E 80FF3A cmp bh,0x3a 00000051 75B8 jnz 0xb 00000053 B8004C mov ax,0x4c00 00000056 CD21 int 0x21
MS-DOSで一文字ずつ出力する方法(ah=02h, int 21h)ではなく、 文字列表示命令(ah=09h, int 21h)を使えばもっと小さくできるかもしれません。