ぐだぐだ低レベルプログラミング(180)x86(16bit)、INC/DECにCISCを見?

Joseph Halfmoon

さて前回よりx86の最初のステップ、16ビットモードに入りました。いままでRISC-V、ArmとRISC系(Armはそれにしちゃ命令多過ぎだが)を練習してきましたが、今回からは「バリバリの」CISCデス。それほど意識することはないけれども、違いは確実にあるので気づいたところから見ていきます。

※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら

※実機動作確認には以下を使用しております。

    •  Raspberry Pi 4 model B、Cortex-A72コア(ARMv8-A)
    •  Raspberry Pi OS (64bit) bullseye
    •  QEMU 5.2.0
    •  FreeDOS 1.3
整数INC/DEC命令

さてx86(16bit)のアセンブリ言語命令の練習の最初に選んだのはINCとDECです。「対象」を+1するのがインクリメントでINC、ー1するのがデクリメントでDECと分かり易いデス。ただx86(16bit)の場合、その対象というのが以下の2つ(詳しく言うと4つ)にわかれます。

    • 整数レジスタの値(バイト幅またはワード幅)
    • メモリの値(バイト幅またはワード幅)

なお、x86世界ではワードは16ビットです。これは元祖の8086が16ビット機だったためだと思います。ArmやRISC-Vの回ではワードは32だったのでご注意を。

さてRISCでもINC/DEC命令はありましたが、操作対象はレジスタだけでした。ここにメモリも入ってくるのがCISCです。RISCで同じことをしようとすれば、メモリ上の値をロード命令でレジスタに読み、レジスタ上でINC/DECして、ストア命令でメモリに書きもどすという3命令を要します。CISCの1命令はこれを1命令でやってしまうのであります。複雑(コンプレックス。)

さて、命令の機能そのものは分かり易いので以下のような8086レベルのアセンブリ言語プログラム(NASM、NASMについてはコチラで)を書いてみました。

        org     100h
section .text

start:
        mov     sp, stacktop
        mov     bx, workb
        mov     bp, workw
        mov     cx, 1000h
        mov     dx, 1234h
test:
        inc     cx
        dec     cx
        inc     dh
        dec     dh
        inc     byte [bx]
        dec     byte [bx]
        inc     word [bp]
        dec     word [bp]
fin:
        mov     ax, 0x4c00
        int     0x21

section .data   align=16
workw:  dw      100h
workb:  db      7

section .bss    align=16
        resb    1024
stacktop:

スタイルとしては、MS-DOSの.COM形式(TINYメモリモデル)のプログラムです。メモリモデルについては別シリーズのコチラで。

ラベル test: 以下が inc、dec を練習してみている部分です。まず16ビットレジスタcxをinc して dec。+1してー1なので元に戻ります。x86の整数レジスタについては前回で。つづいて8ビットレジスタ dh をincしてdec。さらに今度は bxレジスタが指しているメモリ番地をincしてdecします。ここで byte と書いてあるのはbxレジスタの指す先、メモリは [bx] と記します、がバイト幅であるという意味です。

    • 本来のインテル式アセンブラ表記では byte ptr
    • NASMでは byte

インテル式の表記は荘厳ですが、目的はバイトなんだかワードなんだか判別できればいいので、NASM式で十分っす。

メモリのinc/decは、bxで指す先のバイトと、bpで指す先のワードの2種類を練習してますが、ここにはバイトとワード以外の違いもあるのです。それは出てきたところで。

アセンブルして動作を観察

freeDOS上にインストール済のNASMを使って、上記のソースから .COM形式の実行ファイルを作るには以下です。

nasm incdec.asm -fbin -o incdec.com

freeDOSには、MS-DOSのdebugに上位互換(魔改造?されている)のdebugが付属しているので、それを使って動作を観察してみます。

ラベル test: の位置で「止めた」ところからシングルステップで見ていきます。regINCDEC_EC

緑のところで、INC CXしてDEC CXしているのが分かると思います。CXレジスタを見てみると CX=1000(16進なので 0x1000の意味。インテル式表記だと 1000h)から1001となって1000に戻っているのが観察できます。注目すべきはINC CXとDEC CXのオペコードです。黄色で下線引いてますが、1バイトです。とっても短いです。ここでもCISCの特徴が現れました。

x86=CISC=バイト可変長オペコード

です。基本的な命令は短いバイト数、複雑な命令は長いバイト数にエンコードされる原則(方針?)です。ワードレジスタをinc/decする命令は優遇されてます。8086のオペコードマップを見るとこんな感じ。INC_DEC_REGS

全部で256個しかない、1バイトの空間のうち16バイトをワードレジスタのinc/decに費やしてます。なお、よゐこはレジスタ名が、AX、CX、DX、BXとABC順になってないことにも気づくでしょう。その話は前回ね。

一方、赤線のところでは、バイトレジスタ DH を INC、DECしています。DHはDXの上位1バイトなので、これが独立して+1、ー1されているのは予定どおり。しかし、オレンジ色の下線を引いたオペコードをみると2バイトっす。バイトレジスタのinc/decは不遇ね。ワードはポインタの操作やカウンタ操作で頻繁に使うけれども、バイトのinc/decは少ない、って判断があったのでしょう。知らんけど。

さてレジスタ操作に続くのが、お楽しみのメモリ操作です。こんな感じ。

まず d 0130 013Fのところで、inc / dec の対象となっているメモリの内容をダンプして確認してます。黄色の方がワードで BP レジスタが指す先、赤の方がバイトで BX レジスタが指す先です。

黄色の箱の中は 00 01 と書かれてますが、初期値は 0x0100 です。

x86はリトルエンディアン

なので、メモリにおくと、0x0100の下位側の00が下のアドレスに来て、上位側の01が上のアドレスに来ます。

さて、FreeDOSのdebugは、荘厳な BYTE PTR、WORD PTR表現で、各命令をディスアセンブルしてくれる上、命令実行直前の操作対象のメモリ内容を右端に表示してくれます。便利ね。

それをみると

    1. INC BYTE PTR [BX]は、DS:0132=07
    2. INC WORD PTR [BP]は、SS:0130=0100

と表示されてます。DSとかSSって何よと問えば

セグメント・レジスタ名

です。実はBXが指す先とBPが指す先は「異なる」セグメントなのでした。BXはデータセグメントDSを指し、BPはスタックセグメントSSを指します。しかし、ここでは全てのセグメントが同じ番地から始まるTINYモデル(.COM形式)を使っているので、メモリ上はお隣同士だと。

また、よゐこは INC [BX]と INC [BP]のオペコードの長さが異なることにも気づいたでしょう。86命令には結構非対称なところがあるのよ。詳しいことはおいおいと。それは何時だよ?

ぐだぐだ低レベルプログラミング(179)x86、16/32/64bit、整数レジスタの発展? へ戻る

ぐだぐだ低レベルプログラミング(181)x86(16bit)、ADDオペランド何でもありすぎ へ進む