8086命令セットの細かいメモ(2)
今日もまた、細かいけどためになる話を教えてもらったのでメモ。 自分で事後補足として調べたものも含むので、用語が正確じゃないかも。ツッコミ希望。
NASMがpushf(9C)/popf(9D)の機械語をpushfw/popfwとディスアセンブルするのはなぜか?
これはすごく歴史的経緯が深いやつで、16/32/64bitのCPU命令の拡張に伴う変更に起因するもの。 inc/dec命令その他複数の命令も同様の事情を持っている。
instruction prefix
この話に関係するのは、instruction prefix(命令プレフィックス)と呼ばれる、16/32/64bitの切り替えと互換保持などのために用いられる機能。
特に16/32bitの命令切り替えに関連してくるのは、
- The operand-size prefix (66H)
- The address-size prefix (67H)
といった、オペコードの前に置かれるprefix。これが配置されることで、その後ろの命令のオペランドサイズを切り替える。
pushf/popfにおける具体例
表題の疑問の回答。例えば、
bits 16 pushf pushfw pushfd
は、アセンブルすると
9C 9C 669C
となるが、
bits 32 pushf pushfw pushfd
は、
9C 669C 9C
となる。
逆アセンブルの場合も同様で、-b 16
では
9C pushfw 669C pushfd
だが、-b 32
では
9C pushfd 669C pushfw
という解釈になる。 ちなみに、16bit命令のpushf/popfと等価なものはpushfw/popfwであり、pushfd/popfdは32bit用命令セット。
64bitになると、32bit時代の命令であるpushfd/popfd
は廃止され、64bit用にpushfq/popfq
が加わる。
予想だが、16bitのものが残って32bitのものが消えたのは、 16bit機能がリアルモード/プロテクトモードの切り替えで意識する必要があるからではないか、と思っている(正解はまだ知らない)。
REXプレフィックス
64bit化の際には、このprefix以外に、さらにREX prefixが導入された。 その導入のために、inc/dec命令の領域を再配置し、その一部がprefix用に再利用されている。
同じ命令が複数の方法で表現可能な理由
例えば、
mov ax,0x12
は、以下の2通りで表現可能。
B012 C6C012
このように、なくなってもいい or 他の方法で表現できる命令が時々存在する。
何故こんなのがあるかというと、よく使われる命令セットを1つのショートカット機能で実現することでbyte数の節約に役立てる、というのが存在理由らしい。
ここは自分の推測だが、CISCの思想というのはつまり、こういうことなのかもしれない。メモリ大事。
addのImmediate to Register/Memory
命令でs:w = 10
が無い & s:w = 11
でdataが1byteなのはなぜか
フラグの意味は以下のとおり。
加算命令ではあるが、コンピューターの世界では桁あふれと補数の存在によって減算も行われる、というところまでが前提。
sフラグは、wordの世界での下桁1byte分の減算を実現している。
計算としてはsのフラグなしでも全て成り立つのだが、sを導入することで0XFFxx
と加算する場合と比べて1byteが節約できる、というのが(おそらく)このフラグの目的で、同時にs:w = 11
でdataが1byteだった理由。
そして、byteの演算にはsignedを持ち込む必要性がなかったために、s:w = 10
の演算は定義されなかった、ということのようだ。
ざっくりまとめると
- 1byte + 1byte なら引き算考えるときsignとか不要
- 2byte + 2byte も同じくsign不要
- 2byte + 1byte分の引き算をする時、signedで考えると1byteケチれる
という話。
ちょっとした感想
こうしてアセンブラと機械語の対比を見つつ歴史的経緯を知っていくと、一見なんだか意図の読めない命令セットの中の、CPUの進化・効率化と互換性確保のバランスを綱渡りしてる感じが垣間見えて、非常に面白い。
全命令セットを実装していくのは冗長な作業では、と最初は思ったけど、このへんを知るにつけ意味のある作業だと実感が強まってて良い。