Archives

October 2008 の記事

x86 (IA32) の命令は Prefix を追加し op code 長を増やすことで拡張されてきました。
SSE の命令は 2byte escape 0F + 1byte opcode の空間に割り当てられています。
例えば ADDPS は 0F 58 です。バリエーションであるスカラー ADDSS は Prefix と
して F3 が追加され、SSE2 の倍精度命令はさらに 66 と F2 も加わります。

ADDPS     0F 58 ~
ADDSS  F3 0F 58 ~
ADDPD  66 0F 58 ~
ADDSD  F2 0F 58 ~


x64 (AMD64/Intel64/EM64T) の拡張レジスタ xmm8~xmm15 にアクセスするには
上記にさらに REX Prefix が追加されます。
40~4F が REX Prefix で、4bit 分のオプション bit を持っています。

ADDPS     45 0F 58 ~
ADDSS  F3 45 0F 58 ~
ADDPD  66 45 0F 58 ~
ADDSD  F2 45 0F 58 ~


IA32 命令のレジスタフィールドは 3bit しかなく、レジスタ上限は 8個でした。
64bit モードでは REX Prefix 内の機能 bit を追加して、レジスタフィールドを
4bit とみなすことができます。
これで最大 16個のレジスタを指定可能です。

REX Prefix
H---------------L
[0|1|0|0|W|R|X|B]


REX.RXB の 3bit がレジスタ/Index フィールド拡張用です。
残り 1bit REX.W が 64bit オペランドサイズの指定に使われます。

SSE4.x ではさらに opcode が拡張されて 3byte escape の 0F 38 や 0F 3A で
始まるスペースが割り当てられています。(SSE5 は 0F 24)

// SSE4.1
DPPS      66 [REX] 0F 3A 40 ~
BLENDPS   66 [REX] 0F 3A 0C ~
EXTRACTPS 66 [REX] 0F 3A 17 ~

// SSE4.2
CRC32     F2 [REX] 0F 38 F0 ~


追加命令が増える度に命令は長くなる一方です。

AVX でも命令を区別するために、さらに VEX Prefix が定義されています。
今までの命令 Prefix と違うのは、最初から 2~3byte の長さを確保したことと、
その中に従来使われていた Prefix や opcode を組み込んでしまったことです。

例えば SSE 命令の前に付く 66/F2/F3 Prefix は無印を含めて 4通りしかありません。
これは 2bit にエンコードできます。

最初から REX も opcode の機能 bit に組み込んでしまえば 8bit フルに割り
当てる必要もなくなります。

必ず 0F が含まれるとわかっているなら、これを命令から取り除くことが可能です。

2byte-VEX (C5+1byte)
H---C5---L  H---------------L
[11000101]  [R|V|V|V|V|L|P|P]

3byte-VEX (C4+2byte)
H---C4---L  H---------------L   H---------------L
[11000100]  [R|X|B|M|M|M|M|M]   [W|V|V|V|V|L|P|P]


2bit の PP がまさに Prefix の有無を表しています。
 0= なし
 1= 66
 2= F3
 3= F2

L は 256bit (ymm) モードの指定です。0 なら 128bit (xmm)

R X B W は REX Prefix に含まれていた機能 bit です。

4bit の VVVV は追加のレジスタフィールドです。
AVX は 3オペランド命令で source/destination が分離されているので、増えた
レジスタオペランドの指定にここが使われます。

5bit の MMMMM は拡張命令用フィールドでその多くが予約されています。
現在定義されているのは次の 3パターンのみ。
 1= 0F (2byte escape)
 2= 0F 38 (3byte escape)
 3= 0F 3A (3byte escape)

2byte-VEX には MMMMM は含まれていませんが、無条件で 0F とみなします。

これで上で紹介した多くの命令が VEX Prefix で置き換えられることがわかります。
VEX Prefix 以降の 1byte opcode や ModR/M 等のフィールドには互換性があります。

また MMMMM の多くが余っているので、例えば、まず無いとは思いますが

MMMMM: 4= 0F 24

と定義してしまえば AMD SSE5 の VEX 化も可能でしょう。
今後はおそらく命令が長くなることもなく ぐっすり眠れます。


Intel AVX は IA64 のような全く新しい互換性のない命令セットではなく、
x86 (IA32) の命令を圧縮してエンコードしたものです。

追加されたレジスタフィールド以外はこれまでの命令や prefix と同じもの。
従来の命令形式にデコード可能な互換性を保っています。

命令長は必ずしも短くなるわけではなく、ADDPS などは VEX 化することで 1byte
増えそうです。
ただし x64 拡張レジスタへアクセスする場合は長さは変わらず、source と
destination を別指定できるため場合によっては MOV 命令を減らすことができるでしょう。


関連エントリ
Intel AVX その2 転送
Intel AVX



やはり、上位 128bit と下位 128bit を横断する命令は限られているようです。
基本的には 128bit 4要素の SSE 命令を、一度に 2個演算できるのが AVX の
256bit 命令です。

Intel AVX
Intel-AVX-Programming-Reference-319433003.pdf

上下 128bit への任意転送は 128bit 単位のものが多く、SHUFPS/SHUBPD のような
32/64bit 単位で任意に入れ替えたり転送する命令がみあたりません。
float 8個や double 4個の演算時でも、値を入れ替える場合に 128bit 転送命令を
組み合わせる必要がありそうです。

256/128bit 間の転送には VEXTRACTF128, VINSERTF128 が使えます。
上位下位どちらの 128bit を使用するか選択します。
以下 256bit レジスタの ymm.low を下位 128bit、ymm.hi を上位 128bit と表記します。

VEXTRACTF128  xmm1, ymm2, 0   // xmm1= ymm2.low
VEXTRACTF128  xmm1, ymm2, 1   // xmm1= ymm2.hi

VINSERTF128 ymm1, ymm2, xmm3, 0 // ymm1.low= xmm3,  ymm1.hi= ymm2.hi
VINSERTF128 ymm1, ymm2, xmm3, 1 // ymm1.low= ymm2.low,  ymm1.hi= xmm3

VINSERTF128 は VMOVSS 系の部分更新命令なので、合成用の追加のレジスタが増えて
います。
実際は ymm2 に dest (ymm1) と同じレジスタを指定するケースが多いかもしれません。

128bit 単位の入れ替え命令として VPERM2F128 があります。
2つのソース ymm2,ymm3 の任意の 128bit を組み合わせて ymm1 に代入できます。

VPERM2F128  ymm1, ymm2, ymm3, 0  // ymm1.low= ymm2.low,  ymm1.hi= ymm2.low
VPERM2F128  ymm1, ymm2, ymm3, 1  // ymm1.low= ymm2.hi,   ymm1.hi= ymm2.low
VPERM2F128  ymm1, ymm2, ymm3, 2  // ymm1.low= ymm3.low,  ymm1.hi= ymm2.low
VPERM2F128  ymm1, ymm2, ymm3, 3  // ymm1.low= ymm3.hi,   ymm1.hi= ymm2.low
~

VBROADCAST は、唯一 32,64,128bit の任意の値を 256bit に転送できます。
複製されます。

VBROADCASTSS   xmm1, m32   // xmm1 32bit x4= m32, 上位 128bit は 0
VBROADCASTSS   ymm1, m32   // ymm1 32bit x8= m32
VBROADCASTSD   ymm1, m64   // ymm1 64bit x4= m64
VBROADCASTF128 ymm1, m128  // ymm1 128bit x2= m128

F128 は SS, SD, PD, PS に相当し、128bit を表すようです。
(Larrabee では F256 がありそう)

これ以外の命令はほぼ 128bit SSE ×2 個分に相当します。
その他特殊な命令は次の通り。

VZEROALL
VZEROUPPER

VZEROALL は、ymm0~15 レジスタすべてをゼロクリアします。
VZEROUPPER はすべてのレジスタの上位 128bit のみクリア。
これらの命令は、レジスタの部分書き換えが発生してしまう Legacy SSE 命令の
レジスタ依存を断ち切ることが出来ます。

メモリとレジスタ間で条件付き転送が出来るようになっています。
転送単位は 32bit or 64bit で、転送するかどうか mask レジスタで指定します。
mask 値は転送データと同じサイズで、最上位 bit (符号) で判断します。
つまり負なら転送。

VMASKMOVPS  xmm1, xmm2, m128  // 32bit x4, xmm2 が mask
VMASKMOVPS  ymm1, ymm2, m256  // 32bit x8, ymm2 が mask
VMASKMOVPD  xmm1, xmm2, m128  // 64bit x2, xmm2 が mask
VMASKMOVPD  ymm1, ymm2, m256  // 64bit x4, ymm2 が mask
VMASKMOVPS  m128, xmm1, xmm2  // 32bit x4, xmm1 が mask
VMASKMOVPS  m256, ymm1, ymm2  // 32bit x8, ymm1 が mask
VMASKMOVPD  m128, xmm1, xmm2  // 64bit x2, xmm1 が mask
VMASKMOVPD  m256, ymm1, ymm2  // 64bit x4, ymm1 が mask


例えば xmm の各 32bit をシェーダーのように .x .y .z .w で表現すると

xmm2.x= -1
xmm2.y= 0
xmm2.z= -1
xmm2.w= 0

の場合

VMASKMOVPS xmm1, xmm2, m128

は次の転送を行います。

xmm1.x= m128[0]
xmm1.y= 0
xmm1.z= m128[2]
xmm1.w= 0

選択しながら読み出せるため便利ですが、mask レジスタを用意する必要があります。
命令も 3byte 長 VEX (0F38) に含まれています。


積和命令 FMA は AES と同じように別カテゴリに分かれています。
命令フィールドも 0F3A の 3byte VEX で独自のものです。

AVX は基本的に SSE をカバーしていますが SSE1~SSE4.1 までです。
SSE4.2 と AMD SSE5 は含まれていないようです。
積和命令 FMA~ FNMA~ は SSE5 と名称も機能も似通っています。
ちょうど SSE5 を VEX 拡張したかのような位置づけですが、opecode 等を見ても
関連性はありませんでした。


関連エントリ
Intel AVX




2008/10/13
Intel AVX

Intel の新しい拡張命令セットです。基本的には SSE と同じようなもの。

Intel AVX

その特徴は

・256bit になった
・積和命令
・ソース非破壊の 3オペランド命令
・命令 Prefix の圧縮
・メモリアライメント制限の緩和

など。

CPU core 数が増えて性能が向上するように、演算並列度を上げるために SIMD で
扱える bit 幅が拡張されるようです。
x64 の 64bit RAX~ レジスタのように、128bit XMM レジスタが 256bit YMM
レジスタに拡張されています。

256bit = 32bit×8 なので float×8個の演算を一度に行うことができます。
とはいえ GPU の Shader でも xyzw までで、8要素の演算には普段あまり馴染みが
ありません。むしろ 256bit = 64bit×4 と、double で一度に 4要素扱えることに
意義があるのかもしれません。

以前 SSE5 の記事を見たときに勘違いしたのが "3オペランド命令の採用" です。
てっきりソース非破壊で演算可能になったのかと思ったら積和命令のことでした。
(AMD SSE5 Shader のような新しい命令)

今度は本当です。
AVX は単なる 256bit 拡張ではなく、従来の 128bit SSE 命令も含まれています。
VEX Prefix を使った SSE 互換命令はレジスタフィールドが拡張されていて
source operand と destination operand が分離されています。
例えば ADDPS 命令の場合次の通りです。

ADDPS   xmm1, xmm2/m128        // SSE
VADDPS  xmm1, xmm2, xmm3/m128  // AVX
VADDPS  ymm1, ymm2, ymm3/m256  // AVX

積和系 FMA 命令だと 4 operand です。

VFMADDPS  xmm0, xmm1, xmm2/m128, xmm3
VFMADDPS  xmm0, xmm1, xmm2, xmm3/m128
VFMADDPS  ymm0, ymm1, ymm2/m256, ymm3
VFMADDPS  ymm0, ymm1, ymm2, ymm3/m256

SSE3 の HADDPS や SSE4.1 の DPPS もありました。

HADDPS   xmm1, xmm2/m128        // SSE
VHADDPS  xmm1, xmm2, xmm3/m128  // AVX
VHADDPS  ymm1, ymm2, ymm3/m256  // AVX
DPPS     xmm1, xmm3/m128, imm8        // SSE
VDPPS    xmm1, xmm2, xmm3/m128, imm8  // AVX
VDPPS    ymm1, ymm2, ymm3/m256, imm8  // AVX

256bit の VHADDPS も隣接 2値ごとの加算で、機能的には同じ。

問題の 256bit VDPPS ですが、8個全部の内積ではなく上位 128bit と下位 128bit
同士をそれぞれ内積した結果を返すようです。
つまり AVX の 256bit 命令とは、8個の float 演算と言うよりも 4要素の SSE 演算
自体をさらに 2つ同時に実行できると考えた方が理解しやすいようです。

残念なのが VDPPD 命令。
double×4 の内積ではなく 128bit 分 double×2 の内積を 1セット返すのみでした。
float x4 → double x4 への置き換えは簡単ではないようです。

VSHUFPS/VSHUPD も同様に上位 128bit 下位 128bit それぞれの範囲でのみ入れ替え
られます。256bit 全部入れ替えられるわけではないようです。

上位 128bit / 下位 128bit 間の転送には VINSERTF128/VEXTRACTF128 命令が使えます。
任意の xmm レジスタを ymm の上下 128bit どちらに転送するか、
または ymm のどちらの 128bit を xmm に転送するか選択できます。
ちなみに AVX では xmm レジスタへ転送を行うと上位 128bit は 0 クリアされます。

ソース非破壊だとソースを保存のための無駄な転送が減るし、3オペランドだと
演算が MOV を兼ねることが出来ます。
これらは単に命令数が減るメリットだけでなく、レジスタの部分更新が無くなるので
レジスタリネーミングの効率にも貢献するものと考えられます。

例えば MOVSS と MOVPS は AVX では次のように変化します。(m128/m256 は省略)

MOVAPS    xmm1, xmm2    // SSE
VMOVAPS   xmm1, xmm2    // AVX
VMOVAPS   ymm1, ymm2    // AVX

MOVSS   xmm1, xmm2        // SSE
VMOVSS  xmm1, xmm2, xmm3  // AVX

MOVPS は単なる転送命令なので 2 operand のみ。

MOVSS は bit0~bit31 のスカラーを転送する命令です。
SSE では xmm1 の bit32~bit127 は置き換えず保持します。

AVX では dest は全書き換えが原則なので、機能互換性のため bit32~bit127 を
入力する operand が追加されています。(VINSERTF128 も同様)

MOVSS    xmm1, m32    // SSE
VMOVSS   xmm1, m32    // AVX

メモリから読み込む場合 xmm1 の余った bit は 0 クリアされます。
AVX では xmm への転送は上位 128bit をクリアすることになっているため、
bit32~bit255 すべてが 0 になります。
AVX 対応 CPU で SSE 命令を使うと ymm の上位 128bit は非更新です。


Larrabee ではさらに SIMD が 512bit に拡張されるようです。
float だと 16個分ですが、256bit AVX の例を見ると 128bit SSE × 4 相当で
構成されている可能性があります。
また演算毎にレジスタの mask を指定できるようです。
mask の指定はおそらく入力側だと考えられます。shader のような書き込み mask だと、
AVX のルールで考えると VMOVSS のようなもの。そうでなければ GPU のように
内部でスカラー単位に分割して依存を管理しているのかもしれません。


関連エントリ
AMD SSE5 Shader のような新しい命令
SSE についてのメモ(2) SSE4など