原書で学ぶ64bitアセンブラ入門(6)
11章、浮動小数点数について。
原書で学ぶ64bitアセンブラ入門(5) - connpassでの内容です。
chapter11 Floating point instructions
浮動小数点数の演算は、8088世代のCPUでは8087というコプロセッサ(補助プロセッサ)を使っていました。486世代になるとコプロセッサを内包するようになり、現在のCPUでもそれらの為の命令は使えるようです。
ですが、現在では基本的に、完全に分離された浮動小数点数用の機構があり、そちらを使います。その機構では16個の128bitレジスタ(Core iシリーズでは256bit)を使い、これらのレジスタはSSEレジスタ(Streaming SIMD Extensionsレジスタ。SIMD=Single Instruction - Multiple Data)と呼ばれます。
この章では主にこのレジスタを使用した命令を見ていきます
Flaoting point registers
先ほど述べたSSEレジスタはxmm0
というように、xmm
という接頭辞に数字が付いたものになります。16個存在するので、それぞれ
xmm0, xmm1, ... , xmm15
となります。core iシリーズでは256bit長のものになるとも述べましたが、それらはymm
という接頭辞になり
ymm0, ymm1, ... , ymm15
となります。また、以前に64bitレジスタ、たとえばrax
は対応する32bitレジスタeax
と下位32bitを共有しているという話がありましたが、それと同様、ymm
レジスタは対応するxmm
レジスタと下位128bitを共有しています。
加えてcore iシリーズではAVX命令(Advanced Vector Extension命令)が追加されています。
以下、基本的にxmm
のレジスタについて述べていきます。内容はbit長を2倍すればそのままymm
レジスタにも当てはまります。
さて、このxmmレジスタは、一つの値を持つか、もしくは4つのfloatか2つのdoubleという複数の値を持つ事ができます。また、複数の整数値を持つようにもできますが、それについてはこの本では触れないそうです。
Moving data to/from floating point registers
これまた先述した通り、SSEレジスタでは1つの値(scalar値)を扱うか、複数の値を持つデータ(= packed data)として扱う事ができます。これらについてのmov命令がどのようになるか見てみます。
Moving scalars
mov命令のSSE版ということで、movss
,movsd
という命令があります。それぞれfloat用とdouble用です。これらの命令では
- メモリ -> SSE
- SSE -> メモリ
- SSE -> SSE
というパターンで値を移すことができ、SSEレジスタはそれぞれで必要なbit長分の下位bitを使って当該値のやりとりをする事になります。 たとえばmovssした場合はこんな感じ
Moving packed data
さて、今度はpack値でのmov命令です。
今回は命令が4つあり、float用、double用それぞれにalignedとunaligned用の命令があります。ここで言うalignについては後述するとして、表にするとこんな感じに。
aligned | unaligned | |
---|---|---|
float | movaps | movups |
double | movapd | movupd |
命令の命名規則はmov
にalignedの場合はa
、unalingedの場合はu
。続けてpackedのp
、floatならsingleのs
でdoubleならdoubleのd
を続けたパターンになっています。
alignについてですが、これはメモリ上の位置が16バイト境界で整列されていることを意味します。つまり、mov対象の値があるメモリ上のアドレスは、16進数表記の場合に末尾が0となっているということですね。そのような配置になっていないメモリ上の値をmovaps
などすると、segmentation faultを起こします。
適切にalignするためにはalign
命令を使ってalign 16
なんてのを対象となる配列定義直前に入れるか、もしくはmalloc関数で取得したメモリは16バイト境界になっています。
実際に書いてみるとこんな感じに。
align 16
によって配列cの始まる位置が16バイト境界に整えられます。もしこれがalign 16
を入れないなら、cの位置がたまたま16バイト境界に位置しない限りはsegmentation faultを引き起こします。
こんな感じでcのアドレスが16の倍数となるよう、間を適切に空けてくれます。
こんな手間をしなくていいのがunaligned用の命令なんですが、やはりお手軽な分、処理は重いようです。しかし、この本曰くcore i世代のCPUではunalignedでもalignedと同じくらい早くなっているそうです・・・。
まぁ、極限の早さを求める人向きって事ですかね。
加減乗除
本ではそれぞれ節が分かれてますが、内容はほとんど同じなのでまとめて。
命令の命名規則はadd
sub
mul
div
それぞれに、ss
sd
ps
pd
のいずれかが接尾辞として付いた形になります。接尾辞は1文字目がscalarかpackかを表し、二文字目がsingle(=float)かdoubleを表します。addを例に挙げて表にするとこんな感じ
scalar | packed | |
---|---|---|
single | addss | addps |
double | addsd | addpd |
他のsub
mul
div
についても同様です。
レジスタとメモリについて、オペランドに使える組み合わせは以下のようになります
- SSE -> SSE
- メモリ -> SSE
デスティネーションは常にSSEレジスタである必要があります。
整数値の計算を行うとフラグレジスタに値がセットされることがありますが、浮動小数点数での計算では一切のフラグはセットされません。なので、テストをするには後で比較命令を使う必要があります。
packedでの演算は、2つのSSEレジスタ、またはSSEレジスタと同じだけのバイト長のメモリ領域にある、それぞれ対応する位置のpackされた値と演算をします。
コードで表すとこんな感じに。
movapd xmm0, [a] ; load 2 doubles from a
addpd xmm0, [b] ; add a[0] + b[0] and a[1] + b[1]
図示するとこんな感じのイメージ(コードと違ってfloat値4つの場合になってます)
後半に続きます
11章は長いので、一旦ここまで。