ぐだぐだ低レベルプログラミング(182)x86(16bit)、ADD以下同文?微妙に非対称?

Joseph Halfmoon

前回、x86の16ビット命令セットからADD命令を練習。実はADDが分かれば、ADC、AND、XOR、OR、SBB、SUB、CMPの残り7命令も「ほぼほぼ以下同文」てことで理解できます。でも「ほぼほぼ」って何だ?そこで、この辺でCISCらしいx86のオペコードエンコーディングを復習。ほんとメンドクセーんだが、これ。

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

ADD、ADC、AND、XOR、OR、SBB、SUB、CMP

表題にならべました8個の命令は、8086/8088において一族的に扱われております。「ほぼほぼ」同じ機械語命令コードのエンコーディングです。アセンブラ大好きお兄さんお姉さん方には説明の必要もないかとは思いますが機能を列挙します。

    • ADD、整数加算
    • ADC、キャリー付整数加算
    • AND、論理積
    • XOR、排他的論理和
    • OR、論理和
    • SBB、ボロー付き整数減算
    • SUB、整数減算
    • CMP、比較

バイト可変長の8086/8088の機械語において、上記の8命令は、256個しかないファーストバイト(一等地)の51区画を占めています。こんな感じ。以下のHはオペコードバイトの上4ビットのニブル。Lは下4ビットのニブルを意味します。

まずはADD、ADC、AND、XORの核心部分。
ADDregmem
つづいてOR、SBB、SUB、CMPの核心部分。ORregmem

最後に即値オペランド用の部分。ADCimm

さて、今回は命令の実機演習(QEMU使っているのでシミュレータ上だけれども)をお休みして。これらのCISCらしい命令エンコーディングを味わっていきたいと思います。

ADC命令のエンコードを例に

前回ADDだったので、今回はADCです。ADDとADCは計算のときにキャリー(CYフラグの値)を含めるか否かの違いだけで、エンコーディングは以下同文です。バイト可変長な様子が分かる代表例を作ってみましたぞ。8086ADCopcode

 

上記では、オペコードADCがバイト可変長のエンコーディングにより変化する様子を描いてみました。実際にはこれ以外の組み合わせもあるのですが、まあ代表的な様子ということで。左に番号を振ったので、番号順に説明していきます。

1番目のエンコード

1番目は一番ありがちなレジスタ間のADC演算です。演算幅がバイトであるのかワード(x86では16ビット)であるのかはファーストバイトの最下位のwとかかれたビットで判別します。このビットは紫色にしたので、全形式共通であります。

第2バイトの最上位の2ビットはmodフィールドと呼ばれ、第2バイトで指示されるオペランドの形式を示してます。この例のmod=11は、レジスタ間であるという指定です。ビット5,4,3がrrr、ビット2,1,0がRRRとありますがその3ビット2つでレジスタ2つを指定してます。

また、ファーストバイトのビット1はディレクションを示しており、RRRからrrrへなのか、rrrからRRRへなのかを判別してます。RISCのように演算は皆レジスタ間であればこのディレクションビットなどは無用の長物なのですが、x86においてはオペランドの片方がメモリということがあるので、方向指定が役に立ちます。ちなみにmod=00とすると、レジスタ間ではなく、レジスタとレジスタの指すメモリ間の演算に化けます。

ここで、ファーストバイトのビット5,4,3に010というコードが埋め込まれているのを覚えておいてくだされ。このパターンは後のエンコードでも出てくる8種命令の中からADCを識別するためのコードです。

なお、上記の図を見ると直ぐに分かりますが、x86は1バイトのコードを上位から2ビット、3ビット、3ビットの3つのフィールドに分けてデコードするスタイルです。これさえ知っていたらお楽。ホントか?

2番目のエンコード

2番目はレジスタとメモリ間です。演算幅はwビットで、方向はdビットで制御されるのは同じです。違いはmod=01となっているところです。これは演算の片割れがメモリであることを示すとともに、実効アドレス(イフェクティブ・アドレス、EA)の計算にオフセットが含まれることを示してます。オフセットは1バイトであり、符号拡張された上でメモリをポイントしているレジスタ(ベースレジスタ)値に加算されているので、レジスタ前後の+127からー128までのアドレスを指せることになります。

3番目のエンコード

3番目もレジスタとメモリ間です。2番目との違いはオフセットが16ビットワード長のフル幅指定になっていることです。

4番目のエンコード

4番目のエンコードはアキュムレータ(ALまたはAX)優遇の例です。8ビットの御先祖様8080との互換性を大切にした形。ALレジスタに8ビットの即値を演算する専用命令です。これにより後に述べる「一般的」な即値命令より1バイトコードが短くなります。

5番目のエンコード

5番目のエンコードもアキュムレータ優遇です。こちらはAXレジスタに16ビットの即値を演算する専用命令です。

6番目のエンコード

6番目からは「一般的な」即値演算です。まずはバイトレジスタへの8ビット即値の演算。ここで注意すべきなのは、1番目から5番目までのエンコードでファーストバイトに鎮座していた赤の010のパターンが第2バイトに「移っている」ことです。それでも010はADCだと。

7番目のエンコード

7番目は6番目のwビットを1に変更したものです。ワードレジスタへの16ビット即値の演算となります。

8番目のエンコード

8番目はバイト幅のメモリの値と8ビット幅の即値の演算となります。その際メモリアドレスにオフセットを加えるのですが、エンコードされたオフセットは8ビット幅で、それを16ビット幅に符合拡張した上でベースレジスタに加えることになります。即値8ビットそのものはその後に続きます。

9番目のエンコード

9番目もバイト幅のメモリの値に8ビット幅の即値の演算ですが、その際のオフセットは16ビットのフル幅指定となります。

10番目のエンコード

10番目はワード幅のメモリの値にワード幅の即値の演算です。ここでは、オフセットは16ビットのフル幅指定となっています。

まあ、こんな感じで2バイト命令から6バイト命令まで、エンコードされることが分かりました。また、機能的には4番目の形式と同じことを6番目の形式でもエンコードできるなど、ケッコー冗長?なエンコードです。その代わり「ツボにハマればより短い機械語命令」をエンコード可能なように工夫されています。アセンブラのプログラマはその辺分かって命令選べよ、って感じっす。CISCだね~。ただし、BP使ったメモリアクセスのときのオフセット幅の例外とかもあるので、ハンドアセンブルするのはかなりメンドイっす。やらんけど。アセンブラはアセンブラでその辺頑張っているのよ。

「ほぼほぼ」以下同文な理由

最初に、8命令のエンコードは「ほぼほぼ以下同文」といいました。「ほぼほぼ」な理由の部分を再度掲げます。ADCimm

即値命令のところです。符号拡張しない即値演算命令の方には、8命令が全てそろっていますが、符号拡張する即値演算命令には、AND、OR、XORの論理演算3命令が抜けてます(8086のマニュアル上では。)論理演算に即値の符号拡張などねえべよ、という思し召しだと思います。また、0x82のところは、即値の符号拡張とバイト演算というこれまたナシな組み合わせになるので放置されているもの見えます。8086/8088では一等地のファーストバイトに空き地がぽつぽつとあります。

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

ぐだぐだ低レベルプログラミング(183)x86(16bit)、MULのオペランドは1個? へ進む