x86はCISC命令セットであり、RISC命令セットと異なりメモリ上のデータを命令のオペランドに取ることができます。 x86のレジスタは(特に32ビットと64ビットは)おおよそ汎用ですが、一部のレジスタはアドレッシングに使えない制限があります。 よくわからなくなるのでまとめてみました。
16ビット
レジスタは全く汎用ではありません。基本的にはAX
、CX
、DX
を算術演算に使い、BX
、SI
、DI
をアドレス計算に使う、ということが想定されているようです。
基本的には、以下のアドレッシングモードが使えます。
[addr + offset]
addr
は、以下の八種類BX
BP
SI
DI
BX+SI
BX+DI
BP+SI
BP+DI
offset
は、以下の三種類- (何も足さない)
imm8
imm16
[imm16]
ただし、このうち[BP]
(つまり、addr
がBP
で、offset
が「何も足さない」)は、そのエンコーディングが絶対16ビットアドレス指定に奪われているので、使えません。
もちろん、[BP+0]
とすることで、1バイト増えてしまいますがエンコーディングすることは可能です。
なお、BP
をフレームポインタとして使っている場合、[BP]
に入っているのはリターンアドレスのはずなので、普通は使わないと思います。
32ビット
レジスタはほぼ汎用になりました。 基本的には、以下のアドレッシングモードが使えます。
[addr + offset]
addr
は、以下の七種類EAX
EBX
ECX
EDX
ESI
EDI
EBP
offset
は、以下の三種類- (何も足さない)
imm8
imm32
[imm32]
[addr + index * scale + offset]
(SIBバイト使用)addr
は、以下の八種類EAX
EBX
ECX
EDX
ESI
EDI
ESP
EBP
index
は、以下の八種類EAX
EBX
ECX
EDX
ESI
EDI
- (零)
EBP
scale
は、以下の四種類1
2
4
8
offset
は、以下の三種類- (何も足さない)
imm8
imm32
[imm32 + index * scale]
(SIBバイト使用)
16ビットの時からの伝統で、[EBP]
(つまり、addr
がEBP
で、offset
が「何も足さない」)は、そのエンコーディングが絶対32ビットアドレス指定に奪われているので、使えません。
16ビットの時同様、[EBP+0]
とすることで、1バイト増えてしまいますがエンコーディングすることが可能です。
SIBバイト利用アドレッシングでは、[EBP + index * scale]
(つまり、addr
がEBP
で、offset
が「何も足さない」)は、そのエンコーディングが絶対32ビットベースアドレス指定に奪われているので、使えません。
同様に、[EBP + index * scale + 0]
とすることで、1バイト増えてしまいますがエンコーディングすることが可能です。
index
がEBP
ではなくscale
が1
の場合は、逆順にすればバイト数増加はありません。
八つの汎用レジスタの一員であるにもかかわらず、[ESP + offset]
は三種類とも、そのエンコーディングがSIBバイト利用アドレッシングに奪われているため、使えません。
これをやりたい場合は、SIBバイト利用アドレッシングの中にある[ESP + (零)* scale + offset]
を使うことで、1バイト増えてしまいますがエンコーディングできます。
scale
は、1
を設定するのが一般的です。
ESP
は、そのエンコーディングが零レジスタ指定に奪われているので、index
として指定することができません。
とはいえ、ESP
を2倍した値というのが意味を持つ局面はないはずなので、通常は問題となりません。
なお、零レジスタを2倍・4倍・8倍する、というのも意味がないものであり、エンコーディングに無駄があります。
64ビット
汎用レジスタは16本に増えました。 アドレッシングモードは32ビットとほぼ変わりませんが、以下の三点に注意する必要があります。
[imm32]
がなくなり、代わりに[RIP + imm32]
ができるようになった[imm32 + index * scale]
は残存
RSP
の“裏レジスタ”であるR12
は、ModR/MでRSP
が使えないことを引き継いでいるindex
にRSP
が使えない制限は引き継いでいない
RBP
の“裏レジスタ”であるR13
は、(ModR/Mとbase
で)RBP
でできないことを引き継いでいるindex
ではRBP
は自由に使える
つまり、直観的でない点は、
[RIP + index * scale + offset]
はできない[R12]
は[R12 + (零)*1]
で表現されるので1バイト長い[R12 + offset]
は[R12 + (零)*1 + offset]
で表現されるので1バイト長い[R13]
は[R13 + 0]
で表現されるので1バイト長い[R13 + index * scale]
は[R13 + index * scale + 0]
で表現されるので1バイト長いscale
が1
の場合は、逆順にすると短くできることがある
また、エンコーディングの無駄な点(rex.b
が0
か1
かで意味が変化しない)として、
が挙げられます(まぁそもそもrex.x
はSIBバイトを使わない場合、rex.b
はメモリオペランドを取らない場合、rex.w
はバイト命令の場合、は無意味なのですが)。
まとめ
- 16ビットの時は、アドレッシングに使えないレジスタが存在した
- 32ビット以降では、基本的にすべてのレジスタがアドレッシングに使える
- しかし、特定のレジスタを使うアドレッシングは、命令長が伸びることがある
[BP]
、[EBP]
、[RBP]
は1バイトのびる(しかし、フレームポインタとして使っているならばそこに入っているのはリターンアドレスなので、あまり使われない)[EBP + index * scale]
、[RBP + index * scale]
は1バイトのびる(しかし、フレームポインタとして使っていて、index
が素直な配列インデクスであれば、このパターンは出てこないはず)[ESP]
、[ESP + offset]
、[RSP]
、[RSP + offset]
のようにESP
かRSP
がmodR/Mで指定されるとベースだと1バイトのびる(これはよく出てくる)ESP
やRSP
はindex
には使えない(使われることはないはず)
- 64ビットでは、“裏レジスタ”の関係にあるレジスタが
EBP
やESP
のModR/Mとbase
における特性を引き継いでいるindex
では制約はない[R12]
、[R12 + offset]
、[R13]
、[R13 + index * scale]
は1バイトのびる