2. MMX

『8 / 16 / 32bitの整数をまとめて処理できる』というのは発表当時の雑誌に散々解説されていたのでみなさんご存知だと思います.
#割にはあんまり流行ってないですけど
命令の説明のためにここだけの記号法ですが, 次のルールを使います.
8 / 16 / 32bit整数をB, W, Dで表します. それらを複数並べた場合は左のほうが上位bitとします.
(例) [D1:D0]は上位D1下位D0の32bit整数が2つペアになったもの.
m??は??bitメモリ, r32は32bit汎用レジスタを表します.
殆どのMMX命令は第1オペランド (dest) にはMMXレジスタのみ指定でき, 第2オペランド (src) にはMMXレジスタ / メモリを指定できます. シフト命令のみ第2オペランドに即値を使えます.

2.1 読み込み / 書き込み

movq m64, MMX
movq MMX, MMX / m64
64bitデータのやり取りをします. MMXレジスタの上位32bitが下位32bitよりもアドレスの大きい方に格納されます.

movd MMX, r32 / m32
32bitデータDをMMXにゼロ拡張で代入します. MMX=[0:D]
符号拡張する命令は存在しないし, 上位32bitを保存したまま下位32bitに代入することも出来ません. つまりmovzx eax,bxタイプしかないということです.

movd r32 / m32, MMX
MMXの下位32bitデータをr32 / m32に代入します.

2.2 足し算 / 引き算

padd (b / w / d)
padds (b / w)
paddus (b / w)
それぞれラップアラウンド (普通のaddで飽和した時と同じ処理), 符号付き飽和, 符号無し飽和モードで足し算します.
b / w / dはそれぞれ8 / 16 / 32byte整数の組として処理せよということです.

addの所をsubに変えたものは引き算です.

2.3 掛け算

pmul (l / h) w
符号付き16bitの乗算をし, 結果の上位/下位16bitを返します.

レジスタ内容
mm0[W3:W2:W1:W0]
mm1[W3':W2':W1':W0']
mm0*mm1[D3:D2:D1:D0] (Di:=Wi*Wi')

このDiのそれぞれの下位16bitを取り出すのがpmullw, 上位16bitを取り出すのがpmulhwです.

意外と使えない命令です.(:-P

pmaddwd
積和演算をします.

レジスタ内容
mm0[W3:W2:W1:W0]
mm1[W3':W2':W1':W0']
に対して pmaddwd mm0,mm1の後
mm0[W3*W3'+W2*W2':W1*W1'+W0*W0']

(x')   (cosθ -sinθ) (x)
     =
(y')   (sinθ  cosθ) (y)
なんていうのを計算したければ, mm0=[x:y:x:y], mm1=[cosθ:-sinθ:sinθ:cosθ]を (固定小数で入れておき) pmaddwd mm0,mm1の後シフトすればよいです.

2.4 比較

pcmpeq (b / w / d)
mm0とmm1のそれぞれの要素について等しければbit全てに1, そうでなければ0が入ります.

pcmpgt (b / w / d)
mm0とmm1のそれぞれの要素についてmm0の方がmm1より真に大きければbit全てに1, そうでなければ0が入ります.

絶対値やmax/minの計算でCFとsbbを組み合わせて分岐せずに求めるトリックを 簡単に使えるようしたようなものです.

2.5 ビット演算

pand, pandn, por, pxor
各bitに対して名前の通りの論理処理を行います.

andn mm0,mm1は mm0=(not mm0) and mm1のことです.

psll (w / d / q), psrl (w / d / q)
それぞれ論理左シフトと論理右シフトです.

psra (w / d)
算術シフトです.

2.6 パック演算

あまり見慣れない命令です.

packuswb
符号無し飽和パックです.

packuswb mm0,mm1
レジスタ内容
mm0[W3:W2:W1:W0]
mm1[W3':W2':W1':W0']
Bi:=min((unsigned)Wi,255)として
mm0[B3':B2':B1':B0':B3:B2:B1:B0]

packss (wb / dw)
符号付き飽和パックです.上とは違って符合付き飽和でBiを求めます.

何故かpackusdwがありません.
こういう命令の`非'直交性は8086以来の伝統を感じさせます.
#深遠なる理由があるのだろうか?

2.7 アンパック演算

punpck (l / h) (bw / wd / dq) dest, src
符号無しデータの上位 / 下位データを交互に混ぜます.
l / hはdest=[D1:D0],src=[D1':D0']の下位 / 上位を指定します. そしてそのD, D'をbw / wd / dqに応じて8 / 16 / 32bitデータの集まりと見なして交互に混ぜてdestに代入します.
bwの時 D=[B3:B2:B1:B0], D'=[B3':B2':B1':B0']だから mm0=[B3':B3:B2':B2:B1':B1:B0':B0]となる.
wdの時 D=[W1:W0], D'=[W1':W0']だから mm0=[W1':W1:W0':W0]となる.
dqの時は mm0=[D':D]となる...ってわけわかりませんね. 表にしてみましょう.

punpcklbw mm0,mm1
レジスタ内容
mm0**** B3B2B1B0
mm1**** B3'B2'B1'B0'
mm0 B3'B3B2'B2 B1'B1B0'B0

punpcklwd mm0,mm1
レジスタ内容
mm0**W1W0
mm1**W1'W0'
mm0 W1'W1W0'W0

punpckldq mm0,mm1
レジスタ内容
mm0*D
mm1*D'
mm0 D'D

ふう, テキストエディタで書くのは疲れる(笑)
`l'でなく`h'を使った場合は`*'の部分とデータの部分が入れ替わります.
実にややこしいですが, 意外と使えます.
#さて問題, 『MMXテクノロジ最適化テクニック』のこの命令を解説したページにはいくつ間違いがあるでしょう?

特にpunpckldq mm0,mm1によってmm1の下位32bitをmm0の上位32bitに移動することが出来ます.

2.8 FPUとの切り替え

emms
FPUとMMXレジスタは共有しているのでFPUとMMXの切り換えが必要でそのための命令です.


御意見は光成滋生<herumi@nifty.com>までお願いします