🧾

x86の汎用レジスタ

2024/06/15に公開

おことわり: これらのレジスタは厳密には「汎用」とは言えない面もありますが、便宜上汎用レジスタと呼称しています。x86に関して、この用法はIntelの文書でも確認されています。

8008

8008はx86の源流となるプロセッサのひとつで、オペランド長は8bit, アドレス長は14bitです。汎用レジスタに相当するレジスタはA, B, C, D, E, H, Lの7本あります。このうちA, H, Lには以下の役割があります。

  • A: アキュムレータ。デフォルトオペランドとして使用。
  • H/L: メモリの上位アドレスと下位アドレス。

7つしかないのは、メモリオペランドを指定できるようにするためです。命令フォーマット上はレジスタとメモリオペランドが以下のように番号づけられています。

番号 オペランド
0 (000) A
1 (001) B
2 (010) C
3 (011) D
4 (100) E
5 (101) H
6 (110) L
7 (111) M = [H:L]

8008のアセンブリ言語には大きく分けて旧ニーモニックと新ニーモニックの2種類があったようです。たとえば旧ニーモニックでADMという命令は新ニーモニックではADD Mと表記されます。

8080 / 8085

8080 / 8085は8008の後継にあたるプロセッサです。

命令セットアーキテクチャの観点からは、8008と比べて以下のような変更が加えられています。

  • コールスタックのためのリング状のレジスタが廃止され、コールスタックはメモリ上に保存されるようになった。
  • 命令フォーマットの整理
    • IN命令、OUT命令が2バイト命令になった。
    • opcodeが再配置された。
  • IN/OUT命令を長くしたことで空いた空間に新規の命令が追加された。これには以下が含まれる。
    • PUSH/POP系命令
    • レジスタペアを扱う命令

このような変更点はあるものの、命令セットの構造は大きくは変更されていませんでした。特に、8008の新ニーモニックと8080のIntelニーモニックを比較すると、8008のアセンブリ命令はほぼそのまま8080の命令として解釈できることがわかります。[1]

このうちスタック置き場が変わったCALL/RET系命令を除くと命令の意味論もおおむね変化はなく、総じて「8008と8080はソースレベル互換性があった」と言ってよさそうです。

ただし、命令フォーマットは整理されたため、バイナリレベルの互換性はありません。特にレジスタの番号づけは以下のように変更されました。

番号 オペランド
0 (000) B
1 (001) C
2 (010) D
3 (011) E
4 (100) H
5 (101) L
6 (110) M = [H:L]
7 (111) A

この変更はおそらくレジスタペアの導入に関係しています。レジスタペアは以下の4つの対と1つの特別なレジスタからなります。

番号 オペランド 備考
0 (00) B:C 単に B ともいう
1 (01) D:E 単に D ともいう
2 (10) H:L 単に H ともいう
3 (11) A:F PSW と呼ばれる
Fはフラグレジスタ
PUSH, POP命令で利用可能
3 (11) SP LXI, DAD, INX, DCX命令で利用可能

レジスタペアの番号が自然に計算できるように、レジスタの番号が1ずつ動かされたものと考えられます。

8080ではレジスタペアに対して限定的な演算を行うことが可能でした。これを踏まえると、8080は16bit CPUとしての性質も部分的に持っていたと言えそうです。

Z80

Z80はZilog社によるプロセッサで、8080の機械語がほぼそのまま動くような形で実装されています。

ただし、Z80向けのアセンブリはIntelニーモニックとは異なるZilogニーモニックで記述されていたようです。8080までのIntelニーモニック分類が機械語の構造を反映したものだったのに対し、Zilogニーモニックは実行時の意味論に基づいて分類されています。たとえば、Intelニーモニックにおける ADD (A ← A + reg) と ADI (A ← A + imm) はZilogニーモニックでは ADD に統一されていました。後述する8086のアセンブリ(Intel記法)はこの方式に近く、Zilogニーモニックの影響を受けたのかもしれません。

汎用レジスターの観点からは、Z80の以下の拡張が特筆に値します。

  • IX, IY レジスターの追加。
  • EX AF, AF' 命令と EXX 命令によるレジスタ退避領域の追加。

このうちIX, IYは、ベースオフセット方式のアドレッシングモードのサポートとともに追加されていました。 DD と FD がそれぞれ IX, IY 用のプレフィックスとして使われ、これらの接頭辞がある場合は以下のように命令の解釈が変更されます。

  • DDがついている場合
    • HL オペランドが IX オペランドとして再解釈される
    • (HL) オペランドが (IX+d) オペランドとして再解釈される
  • FDがついている場合
    • HL オペランドが IX オペランドとして再解釈される
    • (HL) オペランドが (IX+d) オペランドとして再解釈される
番号 オペランド
0 (000) B
1 (001) C
2 (010) D
3 (011) E
4 (100) H
5 (101) L
6 (110) (HL)
6 (110) (IX+d)
6 (110) (IY+d)
7 (111) A
番号 オペランド 備考
0 (00) BC
1 (01) DE
2 (10) HL
2 (10) IX DDプレフィックス使用時
2 (10) IY FDプレフィックス使用時
3 (11) AF PUSH, POP, EX AF, AF' 命令で利用可能
3 (11) SP

8086 / 8088 / 80186 / 80188

8086は8080の後継にあたる16bitプロセッサであり、現代のx86 CPUはこの8086との互換性を保っています[2]。オペランド長は16bit、論理アドレス長は32bit (16bitセグメント + 16bit線形アドレス)、物理アドレス長は20bitです。

8086には8080とのソースレベル・バイナリレベルの互換性はありませんが、8080アセンブリからのソース・ソース変換により容易に移植できるように設計されています。そのようなツールとしてIntel公式のCONV-86や、MS-DOSに同梱されたTRANS-86などがあったようです。

なお、x86のアセンブリ言語は大きくIntel記法とAT&T記法の2種類が知られています。AT&T記法はオペランド長をニーモニックに含める点と、オペランド順がPDP-11に倣う形になっていてIntel記法と真逆になっている点が特徴です。本稿ではIntel記法を使います。

8086の汎用レジスタは8本あり、以下のように番号づけられています。

番号 レジスタ
0 (000) ax = ah:al
1 (001) cx = ch:cl
2 (010) dx = dh:dl
3 (011) bx = bh:bl
4 (100) sp
5 (101) bp
6 (110) si
7 (111) di

これらのレジスタには以下のような役割があります。

  • ax は拡張アキュムレータ。一部の演算命令のデフォルトオペランド。
  • cx は拡張カウンタ。一部の繰り返し命令のカウンタ。
  • dx は拡張データ。
  • bx は拡張ベース。メモリオペランドのベースレジスタとして利用可能。
  • sp はスタックポインタ。push/popの操作対象。
  • bp はベースポインタ (直前のスタックフレーム)。メモリオペランドのベースレジスタとして利用可能。
  • si はソースインデックス。メモリオペランドのインデックスレジスタとして利用可能。一部の文字列命令のデフォルトオペランド。
  • di は宛先インデックス。メモリオペランドのインデックスレジスタとして利用可能。一部の文字列命令のデフォルトオペランド。

ax, cx, dx, bx の4つはそれぞれ上位バイトと下位バイトに分割して8bitレジスタとして参照することもできます。このときの番号づけは以下の通りです。

番号 レジスタ
0 (000) al
1 (001) cl
2 (010) dl
3 (011) bl
4 (100) ah
5 (101) ch
6 (110) dh
7 (111) bh

実は、8086の汎用レジスタはリリース前はxa, bc, de, hl, sp, mp, ij, ikという名前がつけられていました。つまり、8080と8086の汎用レジスタには以下のような対応関係が想定されていたことがわかります。

  • A = al
  • B = ch, C = cl, B:C = cx
  • D = dh, E = dl, D:E = dx
  • H = bh, L = bl, H:L = bx

これをさらに確かめるにはIntelが提供していた変換プログラム (CONV-86) を参照するのが良さそうですが、これは入手が簡単ではなさそうなので、ここではかわりに TRANS.COM の挙動を参照します。 TRANS.COM はMS-DOSのオリジナルの開発元であるSeattle Computer Products社製のツールで、現在はMS-DOSの一部としてオープンソース化されているため、ソースを読んで挙動を理解することができます。

TRANS.COMは8080からではなく、Z80からの移行を想定して作られていました。ここでのレジスタの対応は以下の通りです。

Z80 8086
3: AF
(PSW)
0: AX
7: A 0: AL
0: BC 1: CX
0: B 5: CH
1: C 1: CL
1: DE 2: DX
2: D 6: DH
3: E 2: DL
2: HL 3: BX
4: H 7: BH
5: L 3: BL
3: SP 4: SP
2: IX 6: SI
2: IY 7: DI

ただし、

  • レジスタペア AF (PSW) は AX に対応しますが、これをそのまま解釈すると A = AH, F = FL になります。しかし実際には、8bitレジスタとして使うときは A = AL という対応関係が想定されていました。
    • AF (PSW) が使われる命令は EX AF, AF', PUSH AF, POP AF の3種類しかありません。そのため、普段は A = AL の対応関係を保っておき、これらの3命令に対応する操作を行いたいときだけ LAHF, XCHG AH, AL, SAHF を使って一時的に AF = AX の関係を確立する仕組みでした。
  • IX/IYに対応する値は普段はメモリ上に保管しておき、間接アドレッシングが必要なときだけSI/DIに読み出す仕組みでした。
  • AFの裏レジスタはBPに対応づけられましたが、このときバイト順は本来のAFとは逆順で保管されます。また、BC, DE, HLの裏レジスタはメモリ上に保管する仕組みでした。

80286

80286は8086の後継にあたる上位互換のプロセッサで、プロテクテッドモードの導入という非常に大きな変化がもたらされました。ただし、汎用レジスタという観点からは大きな変更はないため、本稿では大きくは扱いません。

80386 (i386)

80386は80286の後継にあたる上位互換のプロセッサで、32bit対応とページングの導入が果たされました。

32bit対応により汎用レジスタも拡張されました。既存の16bitレジスタが上位方向に伸長され、これらには "e" を前置する規則的な命名が与えられました。既存のレジスタ名は、新しい32bitレジスタの下位ビットのエイリアスと位置づけられました。

32bit 16bit 8bit(上位) 8bit(下位)
0: eax 0: ax 4: ah 0: al
1: ecx 1: cx 5: ch 1: cl
2: edx 2: dx 6: dh 2: dl
3: ebx 3: bx 7: bh 3: bl
4: esp 4: sp
5: ebp 5: bp
6: esi 6: si
7: edi 7: di

レジスタごとの特別な役割は、16bitの場合と大きくは変わりません。しかし、32bitアドレッシングではアドレッシングモードが以下のように拡張されており、bx/bp/si/diレジスタの特殊性はやや薄れています。

base index scale disp
16bit bx, bp または無し si, di または無し 1 16bit
32bit eax, ecx, edx, ebx,
esp, ebp, esi, edi
または無し
eax, ecx, edx, ebx,
ebp, esi, edi
または無し
1, 2, 4, 8 32bit

x86-64

時代は下り、IA-64を頑張っていたIntelに代わってAMDがx86の64bit化を行いました。64bitモードでは様々な変更点がありますが、汎用レジスタの観点からは以下の3点が特筆に値します。

  • レジスタが64bitに拡張された。
  • レジスタが倍増した。
  • 上記とは独立に、8bitレジスタが4つ追加された。

これらをまとめると以下のようになります。

64bit 32bit 16bit 8bit(上位) 8bit(下位)
0: rax 0: eax 0: ax 4: ah 0: al
1: rcx 1: ecx 1: cx 5: ch 1: cl
2: rdx 2: edx 2: dx 6: dh 2: dl
3: rbx 3: ebx 3: bx 7: bh 3: bl
4: rsp 4: esp 4: sp 4: spl
5: rbp 5: ebp 5: bp 5: bpl
6: rsi 6: esi 6: si 6: sil
7: rdi 7: edi 7: di 7: dil
8: r8 8: r8d 8: r8w 8: r8b
9: r9 9: r9d 9: r9w 9: r9b
10: r10 10: r10d 10: r10w 10: r10b
11: r11 11: r11d 11: r11w 11: r11b
12: r12 12: r12d 12: r12w 12: r12b
13: r13 13: r13d 13: r13w 13: r13b
14: r14 14: r14d 14: r14w 14: r14b
15: r15 15: r15d 15: r15w 15: r15b

まず、eax, ecx, ... をさらに上位ビットに拡張した rax, rcx, ... というレジスタが追加されました。以前の短いレジスタは、読み取り時は64bitレジスタの下位ビットを参照するエイリアスとして機能します。いっぽう、書き込み時は以下のような挙動になります。

  • 32bitレジスタ: ゼロ拡張して64bitレジスタ全体に書き込む。
  • 8bit/16bitレジスタ: 64bitレジスタの上位ビットは保存し、下位ビットのみに書き込む。

これはおそらくパイプライン実行において余計なレジスタ読み取り依存を発生させないための対応ではないかと思います。

次に、レジスタが8個から16個に拡張されました。x86の命令フォーマットは8008以来伝統の 2bit + 3bit + 3bit 構造を保つMod R/Mバイトを持っており、これらによってレジスタの数は8個に制限されていました。AMDはREXという命令プレフィックスに1bitずつ追加の情報をエンコードすることで、これらの命令フォーマットを強引に拡張し、レジスタの数を倍増させることに成功しました。

最後に、8bitレジスタ spl, bpl, sil, dil が追加されました。REXプレフィックスが存在するときは、 ah, ch, dh, bh レジスタのかわりにこれらのレジスタが参照されます。これにより、8~32bitレジスタと64bitレジスタとの関係に一貫性がもたらされました。

既存の0~7番レジスタの役割に大きな変化はありません。アドレッシングモードも32bitモードのときとほぼ同じです。ただし、PC相対アドレッシング ([rip+DISP], [eip+DISP]) が使えるようになったという違いはあり、ABIレベルでの設計には影響を与える可能性があります。

Intel APX

Intel APXはごく最近Intelが提案した最新の拡張です。過去のプロダクト名を考えるとだいぶ紛らわしい名前な気もします。

Intel APXはx86の命令の構造を覆す大きな変更といえます。これには以下が含まれます。

  • 従来の2オペランドの汎用命令がRISC風の3オペランド形式に拡張される。
  • 3オペランド形式では、8bit/16bitレジスタへの書き込み時も上位ビットがゼロクリアされる。
  • 汎用レジスタがさらに倍増し、32個になる。

レジスタを増やす方法はx86-64導入時と同様で、新しい命令プレフィックスを使います。これにはEVEXという既存のSIMD用プレフィックスを汎用命令に転用するか、または新しいREX2プレフィックスを使うことになります。

これらをまとめると以下のようになります。

64bit 32bit 16bit 8bit(上位) 8bit(下位)
0: rax 0: eax 0: ax 4: ah 0: al
1: rcx 1: ecx 1: cx 5: ch 1: cl
2: rdx 2: edx 2: dx 6: dh 2: dl
3: rbx 3: ebx 3: bx 7: bh 3: bl
4: rsp 4: esp 4: sp 4: spl
5: rbp 5: ebp 5: bp 5: bpl
6: rsi 6: esi 6: si 6: sil
7: rdi 7: edi 7: di 7: dil
8: r8 8: r8d 8: r8w 8: r8b
9: r9 9: r9d 9: r9w 9: r9b
10: r10 10: r10d 10: r10w 10: r10b
11: r11 11: r11d 11: r11w 11: r11b
12: r12 12: r12d 12: r12w 12: r12b
13: r13 13: r13d 13: r13w 13: r13b
14: r14 14: r14d 14: r14w 14: r14b
15: r15 15: r15d 15: r15w 15: r15b
16: r16 16: r16d 16: r16w 16: r16b
17: r17 17: r17d 17: r17w 17: r17b
18: r18 18: r18d 18: r18w 18: r18b
19: r19 19: r19d 19: r19w 19: r19b
20: r20 20: r20d 20: r20w 20: r20b
21: r21 21: r21d 21: r21w 21: r21b
22: r22 22: r22d 22: r22w 22: r22b
23: r23 23: r23d 23: r23w 23: r23b
24: r24 24: r24d 24: r24w 24: r24b
25: r25 25: r25d 25: r25w 25: r25b
26: r26 26: r26d 26: r26w 26: r26b
27: r27 27: r27d 27: r27w 27: r27b
28: r28 28: r28d 28: r28w 28: r28b
29: r29 29: r29d 29: r29w 29: r29b
30: r30 30: r30d 30: r30w 30: r30b
31: r31 31: r31d 31: r31w 31: r31b

APXによって追加されるr16~r31は、なるべく既存のr8~r15レジスタと同様の能力をもつように設計されていますが、例外もあります。現時点ではVEXプレフィックス付き命令(のうちEVEXの同等な形式を持たないもの)は拡張EVEX[3]プレフィックス命令に昇格できないため、これらの命令のうちGPRによるアドレッシングが可能なものはrax~r15までのレジスタしか利用できません。

4004 / 4040

4004はIntelの最初のプロセッサです。オペランド長は4bit, アドレス長は8bitです。

4004は8008の先祖にあたるとも考えられますが、命令セット上は隔たりも大きいため、本稿での言及は最小限に留めます。

おまけ: その他のナンバリング

フラグ

8008 8080 Z80 8086
C 0: CY 0: CY 0: CF
1: N
P 2: P 2: P 2: PF
- 4: AC 4: AC 4: AF
Z 6: Z 6: Z 6: ZF
S 7: S 7: S 7: SF
- - 8: TF
- - 9: IF
- - 10: DF
- - 2: O 11: OF

2項演算

0ビット目と1ビット目が入れ替わっているのはともかく、ORとSUBが入れ替わっているのはかなり謎です。どういった事情があったのでしょうか。

8008 / 8080 / Z80 8086
ADD 0 (000) 0 (000)
OR 6 (110) 1 (001)
ADC 1 (001) 2 (010)
SBB 3 (011) 3 (011)
AND 4 (100) 4 (100)
SUB 2 (010) 5 (101)
XOR 5 (101) 6 (110)
CMP 7 (111) 7 (111)

条件コード

cc 8008 8080 Z80 8086
OF=1 5: PE 0: O
OF=0 4: PO 1: NO
CF=1 4: C 3: C 3: C 2: B/NAE/C
CF=0 0: NC 2: NC 2: NC 3: NB/AE/NC
ZF=1 5: Z 1: Z 1: Z 4: Z/E
ZF=0 1: NZ 0: NZ 0: NZ 5: NZ/NE
CF=1 ∨ ZF=1 6: BE/NA
CF=0 ∧ ZF=0 7: NBE/A
SF=1 6: M 7: M 7: M 8: S
SF=0 2: P 6: P 6: P 9: NS
PF=1 7: PE 5: PE 5: PE 10: P/PE
PF=0 3: PO 4: PO 4: PO 11: NP/PO
SF xor OF 12: L/NGE
¬(SF xor OF) 13: NL/GE
ZF=1 ∨ (SF xor OF) 14: LE/NG
ZF=0 ∧ ¬(SF xor OF) 15: NLE/G

セグメントレジスタ

セグメントレジスタの番号はMOV命令 (8C, 8E) のreg fieldの解釈のほか、セグメントオーバーライドプレフィックスの並び順とも整合します。

0 ES
1 CS
2 SS
3 DS
4 FS
5 GS

文献

脚注
  1. Intelのドキュメントをいくつか見た感じだと、ジャンプ命令が8008の新ニーモニックではJUMP, 8080のIntelニーモニックではJMPと表記されているようなので、これだけ対応関係が怪しく感じます。このような些細な理由で互換性がなかったら困りそうなので、この別名についてはアセンブリが何か対応をしていたのかもしれません。 ↩︎

  2. X86-Sで互換性の削除が計画されています。 ↩︎

  3. 「拡張」という単語が同じ術語中に3回登場していますが、間違いではありません。 ↩︎

Discussion