MMX について
MMX 命令では、8つの 64bit レジスタを使用することができます。
64bit 内にパックされた、8bit x 8、16bit x 4、32bit x 2 の整数値を、一つの命令を使って、同時に処理することができるのが特徴です。
画像や音声などの、値が連続している大量のデータに対して、同じ計算を行いたい場合に使用します。
64bit 内にパックされた、8bit x 8、16bit x 4、32bit x 2 の整数値を、一つの命令を使って、同時に処理することができるのが特徴です。
画像や音声などの、値が連続している大量のデータに対して、同じ計算を行いたい場合に使用します。
x64 に MMX は含まれています。
ただし、SSE2 などにも同じような命令があり、そちらでは 128bit レジスタを使うことができるので、可能であればそちらを使った方が良いでしょう。
ただし、SSE2 などにも同じような命令があり、そちらでは 128bit レジスタを使うことができるので、可能であればそちらを使った方が良いでしょう。
AMD プロセッサの場合は、MMX レジスタで浮動小数点数を扱える、3DNow! 命令が使えます。
概要
- MMX では、8つの 64bit レジスタ MMX0〜MMX7 を使用することができます。
- MMX レジスタは、x87 のデータレジスタ FPR0〜FPR7 の下位 64bit に割り当てられます。
そのため、x87 命令と同時に使用することはできません。 - MMX 命令は、rFLAGS レジスタを一切変更しません。
- MMX 命令のオペランドサイズは、オペコードによって決まります。
オペランドサイズ・プレフィックス (66h) や REPx プレフィックス (F2h, F3h) は、本来の用途で使われず、オペコードを拡張するために使われます。 - LOCK プレフィックスは使用できません。
- MMX 命令では、メモリのアライメントは必要ありませんが、アクセスが遅くなる場合があるため、出来れば 8 byte 境界に合わせておいてください。
MMX と x87
x87 と MMX は、レジスタが共有されているので、2つの命令を同時に使用することはできません。
MMX 命令を実行した場合、x87 の各レジスタは、以下のようになります。
このように、MMX 命令を使用した場合は、x87 データレジスタがすべて空ではない状態になるため、直後に x87 命令を使おうとしても、新しい値をプッシュできない状態になっています。
MMX 命令を実行した場合、x87 の各レジスタは、以下のようになります。
- x87 ステータスワード・レジスタの TOP は 0 になる。
- EMMS 命令の実行時を除いて、x87 タグワード・レジスタの値は、すべて空ではない状態になる。
(x87 データレジスタが、すべて空ではない状態となる) - MMX レジスタに値が書き込まれた場合、x87 データレジスタの下位 64bit に書き込まれる。
その場合、80bit 中の上位 16bit ビット (符号と指数のビット) は、すべて 1 になる。
これにより、浮動小数点数として見た場合、x87 レジスタの値は、無限大か NaN になります。
このように、MMX 命令を使用した場合は、x87 データレジスタがすべて空ではない状態になるため、直後に x87 命令を使おうとしても、新しい値をプッシュできない状態になっています。
EMMS 命令
MMX 命令を使用した後、x87 レジスタが使用できる状態にしたい場合は、EMMS 命令を実行する必要があります。
これにより、x87 タグワード・レジスタの値が、すべて空 (ビットがすべて 1) の状態になるので、後続の x87 命令を正しく動作させることができるようになります。
これを、MMX 状態のクリアと呼びます。
アセンブラの関数内で MMX 命令を使った後、関数から戻る前に、EMMS 命令を実行します。
もしくは、MMX 命令を使った関数内で、他の関数を呼び出す前に、EMMS 命令を実行します。
これにより、x87 タグワード・レジスタの値が、すべて空 (ビットがすべて 1) の状態になるので、後続の x87 命令を正しく動作させることができるようになります。
これを、MMX 状態のクリアと呼びます。
アセンブラの関数内で MMX 命令を使った後、関数から戻る前に、EMMS 命令を実行します。
もしくは、MMX 命令を使った関数内で、他の関数を呼び出す前に、EMMS 命令を実行します。
MMX 命令の特徴
MMX 命令では、64bit 内にパックされた複数の整数値を、一度に同時に処理できることと、飽和という処理によって、値を、符号付き/符号なしの値の範囲に収めることができるのが特徴です。
命令の一覧は、MMX 命令 をご覧ください。
命令の一覧は、MMX 命令 をご覧ください。
飽和
例えば、PADDUSB 命令の場合、パックされた8つの符号なし 8bit 整数を、別のパックされた8つの符号なし 8bit 整数と加算して、それぞれの8つの加算結果を、符号なし 8bit 整数の範囲に飽和し、パックされた8つの符号なし 8bit 整数として、宛先に格納します。
つまり、200 + 100 = 300 の場合、加算の結果は、符号なし 8bit 整数の最大値である 255 を超えていますが、飽和された結果、最大値に調整され、255 となります。
飽和の処理がない場合、200 + 100 = 300 & 255 = 44 となります。
飽和とは、整数で表現可能な最小値と最大値の範囲に、値を収めることです。
つまり、200 + 100 = 300 の場合、加算の結果は、符号なし 8bit 整数の最大値である 255 を超えていますが、飽和された結果、最大値に調整され、255 となります。
飽和の処理がない場合、200 + 100 = 300 & 255 = 44 となります。
飽和とは、整数で表現可能な最小値と最大値の範囲に、値を収めることです。
パックとアンパック
もう一つ特徴的なのは、パックとアンパックという処理です。
アンパックは、パックされた整数値を、一つ上のサイズに拡張する時などに使います (8bit → 16bit など)。
オペランドに同じレジスタを指定した場合、下位または上位に、同じ値を並べることもできます。
パックは、2つのパックされた値を元に、一つ下のサイズに飽和付きで縮小して、1つのレジスタに格納したい時に使います。
(16bit x 4 と 16bit x 4 の値から、8bit x 8 の値に変換するなど)
アンパックは、パックされた整数値を、一つ上のサイズに拡張する時などに使います (8bit → 16bit など)。
オペランドに同じレジスタを指定した場合、下位または上位に、同じ値を並べることもできます。
パックは、2つのパックされた値を元に、一つ下のサイズに飽和付きで縮小して、1つのレジスタに格納したい時に使います。
(16bit x 4 と 16bit x 4 の値から、8bit x 8 の値に変換するなど)
アンパック
例えば、8bit の値が、メモリ上に連続して並んでおり、それを 8 byte (64bit) 単位で MMX レジスタに読み込んで、MMX で値を処理した後、再びメモリ上に格納するとします。
この時、8bit の値を演算するために、一度 16bit や 32bit などの大きなサイズに変換しなければならない場合があります。
もしも 16bit サイズで演算したい場合は、読み込んだ8つのバイト値を、2つのレジスタに分けて、16bit x 4 と 16bit x 4 にする必要があります (ゼロ拡張または符号拡張)。
そこからさらに 32bit サイズに拡張する場合は、32bit x 2 のレジスタを4つ使うことになります。
アンパック命令は、下位 32bit または上位 32bit のパック値を、別のパック値と交互に並べます。
これにより、拡張するサイズの下位には第1オペランド、上位には第2オペランドの値が並ぶ形になります。
第2オペランドで 0 の値を指定するとゼロ拡張となり、ビットがすべて 1 の値を指定すると、負の符号拡張にすることができます。
結果として、MMX0 には、元の下位4つのバイト値を拡張した 16bit x 4 の値が格納され、MMX1 には、元の上位4つのバイト値を拡張した 16bit x 4 の値が格納されます。
この時、8bit の値を演算するために、一度 16bit や 32bit などの大きなサイズに変換しなければならない場合があります。
もしも 16bit サイズで演算したい場合は、読み込んだ8つのバイト値を、2つのレジスタに分けて、16bit x 4 と 16bit x 4 にする必要があります (ゼロ拡張または符号拡張)。
そこからさらに 32bit サイズに拡張する場合は、32bit x 2 のレジスタを4つ使うことになります。
アンパック命令は、下位 32bit または上位 32bit のパック値を、別のパック値と交互に並べます。
これにより、拡張するサイズの下位には第1オペランド、上位には第2オペランドの値が並ぶ形になります。
第2オペランドで 0 の値を指定するとゼロ拡張となり、ビットがすべて 1 の値を指定すると、負の符号拡張にすることができます。
ゼロ拡張
以下は、8bit から 16bit にゼロ拡張する例です。movq mmx0, [memory] ; 8byte 読み込み pxor mmx7, mmx7 ; mmx7 = 0 movq mmx1, mmx0 ; mmx1 = mmx0 (2つ目の値) punpcklbw mmx0, mmx7 ; mmx0: 下位32bit の 8bit x 4 -> 16bit x 4 punpckhbw mmx1, mmx7 ; mmx1: 上位32bit の 8bit x 4 -> 16bit x 4 # 最初の値 mmx0: |HGFEDCBA| (8bit x 8) mmx1: |HGFEDCBA| # punpcklbw mmx0, mmx7 (下位32bit アンパック b -> w) mmx0 |----|DCBA| mmx7 |----|0000| -> mmx0 |0D|0C|0B|0A| # punpckhbw mmx1, mmx7 (上位32bit アンパック b -> w) mmx1 |HGFE|----| mmx7 |0000|----| -> mmx1 |0H|0G|0F|0E|
結果として、MMX0 には、元の下位4つのバイト値を拡張した 16bit x 4 の値が格納され、MMX1 には、元の上位4つのバイト値を拡張した 16bit x 4 の値が格納されます。
符号拡張
符号拡張したい場合は、mmx7 において、それぞれの対応するバイトが、元の符号ビットで埋められた値であれば良いので、PANDN 命令と比較命令、または符号付き右シフト命令を使うと良いでしょう。movq mmx0, [memory] ; 8byte 読み込み mov eax, 0x80808080 ; 比較用ソース movd mmx6, eax ; MMX6 = EAX punpckldq mmx6, mmx6 ; MMX6 = 0x80808080_80808080 (上位32bitに同じ値を並べる) movq mmx7, mmx0 ; mmx7 に符号拡張用の値を入れるため、コピー pxor mmx5, mmx5 ; mmx5 = 0 pandn mmx7, mmx6 ; mmx7 をビット反転して mmx6 で AND ; 8bit 単位で、符号ビットが 0 (正) なら 0x80。負なら 0 になる。 pcmpeqb mmx7, mmx5 ; 8bit 単位で、0 と等しければ (負なら) ビットをすべて 1 にする # punpckldq mmx6, mmx6 mmx6 |-|A| mmx6 |-|A| -> mmx6 |AA|
パック
MMX で演算した後、拡張されたサイズの値を、元のサイズの値に戻したい場合は、パック命令を使います。
飽和付きで変換されるため、変換後の整数値で範囲外となる値は、範囲内の値に収められます。
一つ下のサイズにしか縮小できないため、32bit → 8bit にしたい場合は、32bit → 16bit → 8bit と、2回変換する必要があります。
第1オペランドに、下位から拡張した値、第2オペランドに、上位から拡張した値を指定します。
上記の場合は、符号なし 8bit 値として変換されるため、元の値が 0 より小さければ 0、元の値が 255 より大きければ 255 になります。
飽和付きで変換されるため、変換後の整数値で範囲外となる値は、範囲内の値に収められます。
一つ下のサイズにしか縮小できないため、32bit → 8bit にしたい場合は、32bit → 16bit → 8bit と、2回変換する必要があります。
第1オペランドに、下位から拡張した値、第2オペランドに、上位から拡張した値を指定します。
packuswb mmx0, mmx1 ; 符号付き 16bit → 符号なし 8bit (飽和) mmx0 |DD|CC|BB|AA| mmx1 |HH|GG|FF|EE| -> mmx0 |HGFEDCBA|
上記の場合は、符号なし 8bit 値として変換されるため、元の値が 0 より小さければ 0、元の値が 255 より大きければ 255 になります。