ぐだぐだ低レベルプログラミング(193)x86(16bit)、86のNOPはXCHGの別名?

Joseph Halfmoon

過去回でRISC-VやArmのアセンブラしていた時、やたらとこの命令はあの命令のエイリアス(別名)なんてことに遭遇してました。でもエイリアスはRISCどもの専売ではありませぬ。歴史を誇るx86にもしっかりエイリアスが存在してます。まあね、ちょこっとだけれども。エイリアスのお名前はみんな知ってるNOPじゃと。

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

※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。

    •  Windows 11 PC (i5-1235U)
    •  Ubuntu 24.04 LTS on WSL2
    •  QEMU 8.2.2
    •  FreeDOS 1.3
XCHG命令

どこかと別のどこかの内容を交換するXCHG命令(スワップ)は、XCHG命令が無かったとすると同じことを行うのに最低3命令が必要になる命令です(昔は一時退避場所など使わぬのがプロっぽいコードだったが、現代のプロセッサではそうでもない?奥深いのう。。。)

XCHGは、動作としては分かり易い命令ではあるものの、以下の意味でCISC的な命令です。

    1. 1命令で2か所に書き込みする必要がある
    2. そのうちの1か所をメモリ上の変数にしてもよい

そんな「CISCらしい」XCHG命令は、x86上でのNOP命令をも担ってます。第1バイトのオペコードマップ(部分)を見ると以下のようです。xchgOPCODE

つまり、AXとAXを交換するXCHG AX, AX命令こそが、NOPの実体であると。

また例によってオペコードの長さにえこひいきがキツイ命令でもあります。AXレジスタと他の「汎用」レジスタとの間の16ビットデータの交換は1バイト命令ですが、8ビットレジスタ間の交換は2バイト命令です。一方2バイト命令使えばメモリ上の変数とレジスタ間の交換もできます。AX=アキュムレータ優遇は時々でてくる御先祖との互換性のしがらみだと思います。知らんけど。

XCHG命令実験のアセンブリ言語ソース

以下XCHG命令をちょこっと触ってみるだけのソースです。強力なx86用アセンブラNASM用のソースです。ここで以下の2つが並んでいる部分があることをご注意くだされ。

    1. xchg ax, cxとxchg cx, ax
    2. xchg [bx], axとxchg ax, [bx]

x86のオペコードのエンコーディングからすると、1の例では1バイト命令と2バイト命令、2の例では後者のみ正しい書き方という解釈になりますが、NASM(多分他のアセンブラも)意味をくみ取って解釈してくれるので1バイトで書ける命令を2バイトにしたり、オペランドの順番に目くじらたててエラーにしたりはしません。ただし、オブジェクトコードからアセンブリコードを復元するディスアセンブラには伝わらない、ことを後で見るでしょう。

segment code

..start:
    mov ax, data
    mov ds, ax
    mov ax, stack
    mov ss, ax
    mov sp, stacktop
    mov ax, edata
    mov es, ax
test:
    mov ax, 0x1234
    mov cx, 0x5678
    mov dx, 0x9ABC
    nop
    xchg ax, cx
    xchg cx, ax
    xchg dh, dl
    mov bx, v0
    xchg [bx], ax
    xchg ax, [bx]

fin:
    mov ax, 0x4c00
    int 0x21
    resb    2048

segment data    align=16
v0: dw  0xCDEF
    resb    1024 * 63

segment edata   align=16
    resb    1024 * 63

segment stack   class=STACK
    resb    2048
stacktop:
アセンブルして実行

MS-DOS互換で機能強化されたFreeDOS上、以下のステップで上記アセンブラソース xchg.asm から実行可能なオブジェクトファイルを得て実行することができます(nasmとwatcom Cがインストール済であること。)

nasm -f obj xchg.asm
wlink name xchg.exe format dos file xchg.obj
debug xchg.exe

まずは起動直後のディスアセンブル。xchg00

debugはオブジェクトの中に埋め込まれたシンボルやソースを使うわけでなく、オブジェクトコードそのものを素直に逆アセンブルするタイプのデバッガです。よってアセンブラソースではXCHG CX, AXと書かれていたコードがXCHG AX, CXとなってます。

まずは、オペコード 0x90 の XCHG AX, AXがNOPとして働くところを観察。以下の赤色のところが主に関係する部分です。xchg01

AXとAXの内容を交換しても何も変わったように見えんわね。時間を使うだけの(XCHGは2回レジスタに書き込むので8086/8088では単純なMOVよりも1サイクル時間は長くなる)NOP。

つづいてAXとCXの内容を交換2回。優遇されている1バイトのオペコードで出来てますな。xchg02

つづいて、8ビットのレジスタDHとDLを交換っと。注目するのはDX(DXの上8ビットがDH、DXの下8ビットがDL。)xchg03

ただし、バイト操作は2バイト命令になってますな。。。

一方、メモリとレジスタの間のXCHGは以下のように。xchg04

しっかりXCHGできとるようです。当たりまえか。

ぐだぐだ低レベルプログラミング(192)x86(16bit)、PUSH/POPにも先祖の痕跡 へ戻る

ぐだぐだ低レベルプログラミング(194)x86(16bit)、LEA 実効アドレス へ進む