トップアセンブリ言語 > 機械語命令事例(1)

機械語命令事例(1)[2012.10.18追記]

機械語命令はメーカーのマニュアルで確認するのが最も確実であるが、 結構面倒なため、ネットや自分自身のプログラムで事例を探したり、 アセンブラ命令を書いて、MASM32で機械語命令を確認することが多い。 現時点で把握している命令もプログラムの修正で消してしまう可能性も高いので、 事例としてここに残しておく。 いずれ、アルファベット順に整理したい。

1.命令表事例

例えば { "05", "83C0", "add eax,%d", add_eax, -1, 5 }, の先頭の "05"は即値オペランドが4バイトサイズの場合、 "83C0"は1バイトサイズの場合の命令コード(16進表示)である。 最後の 5 は4バイトコードの4と1バイトコードの1を加えた値である。 3列目は命令の文字列表記(ニーモニック)である。 %dは即値オペランドを10進表示するためのものである。 通常のアセンブラでは16進表記が使われるが、少々C言語と相性が悪いので、 自作コンパイラでは10進表記を用いている。 (閑話休題:10進表示と書いたり、10進表記と書いたりと表記のゆれがある。 論文では、許されない。しかし、ホームページでは、敢えて色んな書き方をしている。 これは、検索を考えてのことである。 10進表記でも10表示でもヒットするように考えてのことである。)

[事例1]

[事例2]2012.10.18

2.比較命令

比較命令CMPの結果はフラグに反映される。フラグの状態をレジスタにロードするには SETcc 命令を使用する。

== !=              …  CMP op1,op2 / JZ
eaxに真偽をセット … CMP op1,op2 / MOV EAX,0 / SETcc AL
jz                 … CMP op1,op2 / SETcc AL  / TEST AL,AL / JZ

if (op1 > op2)    … CMP op1,op2 / SETcc AL  / TEST AL,AL / JZ
ニーモニック(cc) テストされる条件命令サブコード ステータス・フラグの設定
O Overflow0000OF = 1
NONo overflow0001OF = 0
B
NAE
Below
Neither above nor equal
0010CF = 1
NB
AE
Not below
Above or equal
0011CF = 0
EZEqual Zero0100ZF = 1
NE
NZ
Not equal
Not zero
0101ZF = 0
BE
NA
Below or equal
Not above
0110(CF OR ZF) = 1
NBE
A
Neither below nor equal
Above
0111(CF OR ZF) = 0
SSign1000SF = 1
NSNo sign1001SF = 0
P
PE
Parity
Parity even
1010PF = 1
NP
PO
No parity
Parity odd
1011PF = 0
L
NGE
Less
Neither greater nor equal
1100(SF XOR OF) = 1
NL
GE
Not less
Greater or equal
1101(SF XOR OF) = 0
LE
NG
Less or equal
Not greater
1110((SF XOR OF) OR ZF) = 1
オペコード命令説明
0F 97 SETA r/m8 より上(CF=0 およびZF=0)の場合バイトを設定する。
0F 93 SETAE r/m8 より上か等しい(CF=0)場合バイトを設定する。
0F 92 SETB r/m8 より下(CF=1)の場合バイトを設定する。
0F 96 SETBE r/m8 より下か等しい(CF=1 またはZF=1)場合バイトを設定する。
0F 92 SETC r/m8 キャリーがある(CF=1)場合設定する。
0F 94 SETE r/m8 等しい(ZF=1)場合バイトを設定する。
0F 9F SETG r/m8 より大きい(ZF=0 およびSF=OF)場合バイトを設定する。
0F 9D SETGE r/m8 より大きいか等しい(SF=OF)場合バイトを設定する。
0F 9C SETL r/m8 より小さい(SF<>OF)場合バイトを設定する。
0F 9E SETLE r/m8 より小さいか等しい(ZF=1 またはSF<>OF)場合バイトを設定する。
0F 96 SETNA r/m8 より上でない(CF=1 またはZF=1)場合バイトを設定する。
0F 92 SETNAE r/m8 より上でなく等しくない(CF=1)場合バイトを設定する。
0F 93 SETNB r/m8 より下でない(CF=0)場合バイトを設定する。
0F 97 SETNBE r/m8 より下でなく等しくない(CF=0 およびZF=0)場合バイトを設定する。
0F 93 SETNC r/m8 キャリーがない(CF=0)場合バイトを設定する。
0F 95 SETNE r/m8 等しくない(ZF=0)場合バイトを設定する。
0F 9E SETNG r/m8 より大きくない(ZF=1またはSF<>OF)場合バイトを設定する。
0F 9C SETNGE r/m8 より大きくなく等しくない(SF<>OF)場合設定する。
0F 9D SETNL r/m8 より小さくない(SF=OF)場合バイトを設定する。
0F 9F SETNLE r/m8 より小さくなく等しくない(ZF=0 およびSF=OF)場合バイトを設定する。
0F 91 SETNO r/m8 オーバーフローがない(OF=0)場合バイトを設定する。
0F 9B SETNP r/m8 パリティがない(PF=0)場合バイトを設定する。
0F 99 SETNS r/m8 符号がない(SF=0)場合バイトを設定する。
0F 95 SETNZ r/m8 ゼロでない(ZF=0)場合バイトを設定する。
0F 90 SETO r/m8 オーバーフローがある(OF=1)場合バイトを設定する。
0F 9A SETP r/m8 パリティがある(PF=1)場合バイトを設定する。
0F 9A SETPE r/m8 パリティが偶数(PF=1)の場合バイトを設定する。
0F 9B SETPO r/m8 パリティが奇数(PF=0)の場合バイトを設定する。
0F 98 SETS r/m8 符号がある(SF=1)場合バイトを設定する。
0F 94 SETZ r/m8 ゼロ(ZF=1)の場合バイトを設定する。
0x9F LAHF

EFLAGS(SF:ZF:0:AF:0:PF:1:CF)をAHにロードします
詳細
EFLAGSの下位バイト(ステータスフラグのSF、ZF、AF、PF、CF)をAHに転送します。EFLAGSの
予約ビット1、3、5はそれぞれ1、0、0の値となります

3.複合代入演算

C言語には a += b; のような複合代入演算子として += -= *= /= %= <<= >>= &= ^= |= がある。

a <op> b; のTCCおよびGCCによるコンパイル結果を下に示す。

TCCの場合は a += b を a = a + b と展開した形となっている。

GCCの場合は add [esp+0Ch],eax によって、メモリの内容を更新している。

a += b の場合、プログラム行数はGCCはTCCの半分で済んでいる。 しかし、2倍速いとは限らない。 一般には、レジスタ間のデータ移動より、メモリとMPU間のデータ移動の方が時間がかかる。 add [esp+0Ch],eax の場合、まず、esp+0Ch番地のメモリの内容がMPUに取り込まれ、 eax と加算が行われる。その結果がesp+0Ch番地に書き込まれる。

すなわち、メモリとMPU間のデータ移動回数は、TCC、GCC いずれも3回である。 このことから、2倍ではないが、多少はGCCの方が高速と推定される。

[TCC]
0040100A 8B45FC                 mov     eax,[ebp-4]     ; eax=a
0040100D 8B4DF8                 mov     ecx,[ebp-8]     ; ecx=b
00401010 01C8                   add     eax,ecx         ; eax=a + b
00401012 8945FC                 mov     [ebp-4],eax     ; a += b

00401015 8B45FC                 mov     eax,[ebp-4]
00401018 8B4DF8                 mov     ecx,[ebp-8]
0040101B 29C8                   sub     eax,ecx
0040101D 8945FC                 mov     [ebp-4],eax     ; a -= b

00401020 8B45FC                 mov     eax,[ebp-4]
00401023 8B4DF8                 mov     ecx,[ebp-8]
00401026 0FAFC1                 imul    eax,ecx
00401029 8945FC                 mov     [ebp-4],eax     ; a *= b

0040102C 8B45FC                 mov     eax,[ebp-4]
0040102F 8B4DF8                 mov     ecx,[ebp-8]
00401032 99                     cdq
00401033 F7F9                   idiv    ecx
00401035 8945FC                 mov     [ebp-4],eax     ; a /= b

00401038 8B45FC                 mov     eax,[ebp-4]
0040103B 8B4DF8                 mov     ecx,[ebp-8]
0040103E 99                     cdq
0040103F F7F9                   idiv    ecx
00401041 8955FC                 mov     [ebp-4],edx     ; a %= b

00401044 8B45FC                 mov     eax,[ebp-4]
00401047 8B4DF8                 mov     ecx,[ebp-8]
0040104A D3E0                   shl     eax,cl
0040104C 8945FC                 mov     [ebp-4],eax     ; a <<= b

0040104F 8B45FC                 mov     eax,[ebp-4]
00401052 8B4DF8                 mov     ecx,[ebp-8]
00401055 D3F8                   sar     eax,cl
00401057 8945FC                 mov     [ebp-4],eax     ; a >>= b

0040105A 8B45FC                 mov     eax,[ebp-4]
0040105D 8B4DF8                 mov     ecx,[ebp-8]
00401060 21C8                   and     eax,ecx
00401062 8945FC                 mov     [ebp-4],eax     ; a &= b

00401065 8B45FC                 mov     eax,[ebp-4]
00401068 8B4DF8                 mov     ecx,[ebp-8]
0040106B 31C8                   xor     eax,ecx         ; a ^= b
0040106D 8945FC                 mov     [ebp-4],eax

00401070 8B45FC                 mov     eax,[ebp-4]
00401073 8B4DF8                 mov     ecx,[ebp-8]
00401076 09C8                   or      eax,ecx
00401078 8945FC                 mov     [ebp-4],eax     ; a |= b
[GCC]
004013CE 8B442408               mov     eax,[esp+8]
004013D2 0144240C               add     [esp+0Ch],eax           ; a += b

004013D6 8B442408               mov     eax,[esp+8]
004013DA 2944240C               sub     [esp+0Ch],eax           ; a -= b

004013DE 8B44240C               mov     eax,[esp+0Ch]
004013E2 0FAF442408             imul    eax,[esp+8]
004013E7 8944240C               mov     [esp+0Ch],eax           ; a *= b

004013EB 8B44240C               mov     eax,[esp+0Ch]
004013EF 99                     cdq
004013F0 F77C2408               idiv    dword ptr [esp+8]
004013F4 8944240C               mov     [esp+0Ch],eax           ; a /= b

004013F8 8B44240C               mov     eax,[esp+0Ch]
004013FC 99                     cdq
004013FD F77C2408               idiv    dword ptr [esp+8]
00401401 8954240C               mov     [esp+0Ch],edx           ; a %= b

00401405 8B442408               mov     eax,[esp+8]
00401409 88C1                   mov     cl,al
0040140B D364240C               shl     dword ptr [esp+0Ch],cl  ; a <<= b

0040140F 8B442408               mov     eax,[esp+8]
00401413 88C1                   mov     cl,al
00401415 D37C240C               sar     dword ptr [esp+0Ch],cl  ; a >>= b

00401419 8B442408               mov     eax,[esp+8]
0040141D 2144240C               and     [esp+0Ch],eax           ; a &= b

00401421 8B442408               mov     eax,[esp+8]
00401425 3144240C               xor     [esp+0Ch],eax           ; a ^= b

00401429 8B442408               mov     eax,[esp+8]
0040142D 0944240C               or      [esp+0Ch],eax           ; a |= b

4.浮動小数点演算命令

FPUのスタックからメインメモリ上のスタックにデータを移したり、その逆を行う命令を調べた。 FPUスタックは8個しかない。 例えば、次のような式ではスタックオーバフローとなる。

    y = x + (x + (x + (x + (x + (x + (x + (x + 1.0)))))));

一番内側の式から遡ればスタックは殆ど使用しないためオーバフローは起きない。 しかし、一般的には、C言語など多数の言語で、左から順に計算しなければならない。

例えば、n = 1; m1 = n++ + (n * 10 + 1); の結果は m1 = 22 である。

( ) 内の計算を先にして、n = 1; m2 = (n * 10 + 1) + n++; とすると、m2 = 12 となる。

つまり計算順序を勝手に変更してはならない。

単純な下向き構文解析でスタックの制限をなくすには、FPUのスタックに途中データを残さず、 メインメモリのスタックに移し、実際に計算するときにFPUのスタックに戻すことにすれば、実際上無限に使える。

転送のロスタイムはあるが、浮動小数点演算に要する時間に比較すれば小さいことが多い。 (加減算などは速いが、割り算は数10クロックかかる) また、この転送はスタックオーバフローの危険性があるケースに限定すれば、 通常のアプリケーションプログラムではこの転送は起きない。 つまり、8個以上のスタックを使うプログラムは極めて特殊なプログラムと言える。 恐らく大抵の場合はスタックを殆ど使わないプログラムに書き換えることができるであろう。

因みに、これまでのテストプログラム(ニュートン法による平方根、立方根の計算)ではスタックは2個しか使用していない。

0040102E DB0424                 fild    dword ptr [esp]
00401031 DD1C24                 fstp    qword ptr [esp]
00401034 DD5C2408               fstp    qword ptr [esp+8]
00401038 DD0424                 fld     qword ptr [esp]
0040103B DD442408               fld     qword ptr [esp+8]
         D9C9                   fxch	; st と st(1) を入れ替え

二項演算で浮動小数点演算結果(左辺)をメインスタックに移すには

   83EC08    sub     esp,8
   DD1C24    fstp    qword ptr [esp]
とする。右辺の値がFPUスタックのトップに置かれた段階で、 左辺をメインスタックからFPUスタックに戻すと、 FPUスタックのトップ st は左辺、次 st(1) が右辺となる。

もし、このような転送を行わなければ、トップが右辺、次が左辺となる。

例えば加算、乗算では問題ないが、減算、除算では演算命令を変えねばならない。 この変更を避けるために、fxch 命令でトップと次を入れ替える。

よって、退避した浮動小数点データを FPUスタックに戻す命令は下のようになる。

   DD0424    fld    qword ptr [esp]
   D9C9      fxch
   83C408    add    esp,8

この命令を自作JITコンパイラに組み込むと、上の例題で ( ) が20個のケースで正常計算ができることを確認した。


SIMPLY FPU by Raymond Filiatreault Chap. 8 Arithmetic instructions - with REAL numbers
FSUB (Subtract two floating point values)
Syntax:    fsub Src
           fsub Dest,Src
fsub real8_var      ;subtract the value of the real8_var variable from ST(0)
fsub dword ptr[eax] ;subtract the REAL4 value pointed to by EAX from ST(0)
fsub st,st          ;would result in a value of 0.0 in ST(0)
fsub st,st(3)       ;subtract the value of ST(3) from ST(0), result in ST(0)
fsub st(3),st       ;subtract the value of ST(0) from ST(3), result in ST(3)
FSUBR (Subtract in reverse two floating point values)
Syntax:    fsubr Src
           fsubr Dest,Src
fsubr real8_var      ;subtract ST(0) from the value of the real8_var variable
                     ;and store the result in ST(0)
fsubr dword ptr[eax] ;subtract ST(0) from the REAL4 value pointed to by EAX
                     ;and store the result in ST(0)
fsubr st,st          ;would result in a value of 0.0 in ST(0)
fsubr st,st(3)       ;subtract the value of ST(0) from ST(3), result in ST(0)
                     ;ST(3) remains unchanged
fsubr st(3),st       ;subtract the value of ST(3) from ST(0), result in ST(3)
                     ;ST(0) remains unchanged
FDIV (Divide two floating point values)
Syntax:    fdiv Src
           fdiv Dest,Src
fdiv real8_var       ;divide ST(0) by the value of the real8_var variable
fdiv dword ptr [eax] ;divide ST(0) by the REAL4 value pointed to by EAX
fdiv st,st           ;would result in a value of 1.0 in ST(0)
fdiv st,st(3)        ;divide ST(0) by the value of ST(3), result in ST(0)
fdiv st(3),st        ;divide ST(3) by the value of ST(0), result in ST(3)
FDIVP (Divide two floating point values and pop ST(0))
Syntax:    fdivp st(i),st
fld   real10_var ;load the temporary value
                 ;-> ST(0)=real10_var, ST(1)=original ST(0)
fdivp st(1),st   ;divide the original ST(0) by real10_var and discard it
                 ;-> ST(0)=(original ST(0) ? temporary value)
FDIVR (Reverse divide two floating point values)
Syntax:    fdivr Src
           fdivr Dest,Src
fdivr real8_var       ;divide the value of the real8_var variable by ST(0)
                      ;and store the result in ST(0)
fdivr dword ptr [eax] ;divide the REAL4 value pointed to by EAX by ST(0)
                      ;and store the result in ST(0)
fdivr st,st           ;would result in a value of 1.0 in ST(0)
fdivr st,st(3)        ;divide ST(3) by the value of ST(0), result in ST(0)
                      ;ST(3) remains unchanged
fdivr st(3),st        ;divide ST(0) by the value of ST(3), result in ST(3)
                      ;ST(0) remains unchanged

5.IA-32 push and pop instructions

InstructionOperationNotes
push word_opr esp = esp - 2
memory[esp] = word_opr
Push word onto stack
pop word_opr word_opr = memory[esp]
esp = esp + 2
Pop word off of stack
push dword_opr esp = esp - 4
memory[esp] = dword_opr
Push dword onto stack
pop dword_opr dword_opr = memory[esp]
esp = esp + 4
Pop dword off of stack
pushfd esp = esp - 4
memory[esp] = EFLAGS
Push EFLAGS onto stack
popfd EFLAGS = memory[esp]
esp = esp + 4
Pop EFLAGS off of stack