前回から元祖x86、16bitCPU、8086/8088の命令セットの復習を始めました。うん十年ぶり。忘れてます。しかしArmとかRISC-Vとかの後に改めて8086の命令をみると確かに複雑、CISCだからあたりまえか。今回は整数足し算、ADD命令を練習してみます。命令にとれるオペランドがいろいろありすぎ、何でもあり?
※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら
※実機動作確認には以下を使用しております。前回はRaspberry Pi 4(Armコア)機上でQEMUでCPUエミュレーションしていましたが、今回からはx86_64のPC上で動かしてます。どうせQEMU上のFreeDOSなのでどこで動かそうと同じじゃと(実はいろいろあるけれど。)
整数ADD命令
前回はINC/DEC命令で控えめに始めましたが、今回はADD命令でまさに整数演算命令の本丸に踏み込みます。ArmやRISC-Vみてから8086のADD命令みると驚愕するのが以下であります。
-
- オペランドにレジスタでも、メモリでも、即値データでも何でもとれる
- 2オペランド、左にデスティネーション兼ソース、右にソースを置く
Armにせよ、RISC-Vにせよ、RISCでは演算はレジスタ間、メモリへのアクセスはロード/ストア命令と峻別されているので、演算命令であるADDのオペランドにメモリをとれるというのはビックリです。まあ、歴史的に言えば8086のCISCのスタイルが先だったのだけれども。
また、2オペランドスタイルで演算結果がソースの片方を上書きしてしまうというのも「そこそこビックリ」かと。RISCは3オペランドで両方のソースを保持することもできるからね。でもコマケーことを言うとArmのメモリ節約用Thumb命令は2オペランドスタイルで8086同様に片方上書きしてしまうタイプです。
ADD命令がとれるオペランドを要約すると以下のようです。
Destination/Source1 | Source2 |
---|---|
register | register |
register | memory |
memory | register |
register | immediate |
memory | immediate |
accumulator | immediate |
唯一の「救い」はADD命令に関してオペランドの両方がメモリという組み合わせが許されてないことですな。ただバリバリのCISC、8086には「その手の」命令も無くはないのですが、また今度ね。
8086/8088レベルのADD命令テストプログラム
NASM(NASMについてはコチラの回で)用、メモリモデルはTINY、DOS上での実行形式は .COM 形式となる予定のアセンブリ言語プログラムが以下に。
org 100h section .text start: mov sp, stacktop mov bx, workb mov si, workw mov bp, sworkw mov word [bp], 4000h mov ax, 1234h mov cx, 3000h mov dx, 5555h test: add ax, 2000h add al, 0Ah add cx, dx add al, [bx] add cx, [si] add [bp], dx add byte [bx], 33h fin: mov ax, 0x4c00 int 0x21 section .data align=16 workw: dw 1000h workb: db 7 section .bss align=16 sworkw: resw 4 resb 2048 stacktop:
startは「伝統の」0100h(インテル式、0x100)、ラベル test: から fin: の間が今回練習してみたADD命令です。
freeDOS上でのNASMによるアセンブルは以下で。
nasm add.asm -fbin -o add.com
生成された add.com をfreeDOS付属のdebugで開いたところが以下に(debugはMS-DOS付属のデバッガと同じファイル名で機能も「互換」ではありますが、何気にアチコチ強化されているfreeDOSのプログラムです。)
上記の赤線部分は、初期化後のレジスタの値と、これから操作するメモリの初期値です。
赤線はAXレジスタへの即値 0x2000の足し込み、緑線は AXの下8ビット分であるALへの即値 0x0Aへの足し込みです。結果をみるとADDされていることが分かります。ここでAX、ALは「アキュムレータとして優遇」されていて、他のレジスタに対してよりも「短い命令」にアセンブルされてます。ここは前回も触れたさらにご先祖の8080の命令セットからの移植の配慮ね。
ピンク色ではフツーのレジスタ間ADD。
最初のピンク線は、バイト幅の計算で、BXレジスタの指す先のバイトをもってきてALレジスタと加えて、ALレジスタに書き出すもの。
次の赤線は、ワード幅(x86では16ビット)で、SIレジスタの指す先のワードをもってきてCXレジスタの値に足し込むもの。
その次の黄色線は、ワード幅で[BP+0]番地から読んだ内容とDXレジスタの内容を加算して[BP+0]番地に書き戻すもの。なお前回もやりましたが、アドレシングのためのベースレジスタにBPをとるとスタックセグメントSSを指すことになります。ただし例によってで、.COM形式なので全てのセグメントは重なってます。
最後の緑線は[BX]番地のバイトに0x33を足し込むもの。メモリから読んだ値に即値を足して、メモリに書き戻す命令です。この命令の場合、[BX], 33 などとオペランド書いてしまうと「ワードだか、バイトだか判断できん」ということになります。そこで荘厳にも
BYTE PTR
と修飾されてます(インテル式。NASMでは素っ気なく BYTEとだけ)
オペコードは1個なんだけれども、いろいろ出来すぎ8086。