※NECさんより正確な情報を頂くことができたので、一部を修正しました。ただし、当時の試行錯誤も自身にとっての記録として重要なので、最低限の修正に留めました。
はじめに
今回もマスクです。前回の調査で、マスク型は__vm
だとわかりました。今度はこの初期値をどうやって入れるかを調べようかと思います。というのは、ベクトル命令では256未満の長さの要素に対して演算を掛けることができ、
そのために、必要なくてもマスク変数を入力しなければならないようだからです。すると、初期値で全てbitの立ったマスクや、全てbitの寝ているマスクを初期値として作りたいわけですね。
やってみましょう。
追記: SXのintrinsic命令では、第四引数のmaskを省略して、第五引数のベクトル長だけを指定する(正確には第四引数に整数型でベクトル長を指定する)ことができます。
マスク型のサイズ
web上の仕様書らしきものを読むと、マスク型は256bitとありますので、そのとおりだとすると、32byteのサイズの型になっていそうです。
実際に調べましょう。次のコードを実行すると
#include <cstdio>
typedef double __vr __attribute__((__vector_size__(2048)));
int main(){
printf("size of __vr = %d [byte]\n", (int)sizeof(__vr));
printf("size of __vm = %d [byte]\n", (int)sizeof(__vm));
return 0;
}
出力は次のようになりました。
size of __vr = 2048 [byte]
size of __vm = 32 [byte]
いいですね。マスク型__vm
は予想通り32byteでした。
立っているビットを数える
まず、ビットが立っているかどうかを確認する術として、ここでは立っているビット数を数えるベクトル命令__builtin_ve_pcvm
を利用します。こんな感じで利用します
__vm a;
int num_bits = __builtin_ve_pcvm(a);
これはマスク変数aの中で立っているビットを数えて、int型の数値として返してくれます。
とりあえず今回は全てのビットを寝かした初期化と、全てのビットを立たせた初期化を考えます。よって、若干雑な確認ですが、この命令での戻り値が0か32であれば期待通りという事にします。
初期化(上手くいった例)
実はマスク変数は定義するだけで初期値として全ビットが寝ている可能性があります。ただし仕様書がないので、念のため全ビットを寝かします。
全ビットを寝かせる場合:
__builtin_ve_xorm
命令を使います。これは、第2引数と第3引数のマスクをXOR演算して、第1引数にセットする命令です。これを使って、自分自身とXOR演算することで必ずビットは0になります。
__vm mask;
__builtin_ve_xorm(mask, mask, mask);
追記: 初期化時に何も初期値を指定してなかった場合は、全てのビットが0で初期化されるそうです。
全ビットを立たせる場合:
__builtin_ve_eqvm
命令を使います。これは、第2引数と第3引数のマスクをXOR演算して、さらにその結果のNOTを取ってから、第1引数にセットする命令です。これを使って、自分自身とXOR演算してビットを0にしてから、さらに反転するので、結果的に全ビットが1になります。
__vm mask;
__builtin_ve_eqvm(mask, mask, mask);
まとめたテストコードはこんな感じです。
#include <cstdio>
int main(){
__vm mask;
__builtin_ve_xorm(mask, mask, mask);
int num_bits = __builtin_ve_pcvm(mask);
printf("num. stand bit = %d\n", num_bits);
__vm mask2;
__builtin_ve_eqvm(mask2, mask2, mask2);
num_bits = __builtin_ve_pcvm(mask2);
printf("num. stand bit(2) = %d\n", num_bits);
return 0;
}
結果は次のようになりました。
カウントされたビット数から、maskの全ビットは寝ており、mask2の全ビットが立っていることが確認できました。
追記:任意の値で初期化する場合
初期値として任意の値をセットしたい場合はままあります。
SXのvector intrinsicにおいて、ベクトルマスク型は64bit符号無し整数×4要素の配列の様に初期化できます。これは次のように括弧{...}
を使って指定します。
//256bit全て0
__vm mask1 = {0};
//256bit全てが1
__vm mask2 = {0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF };
//0-63bitまでが1で64-255bitは0.
__vm mask3 = {0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF};
//任意の値で初期化する(64bit 符号無し整数×4)
__vm mask4 = {0x12345678, 1, 2, 3};
三番目の例のように要素が足りないときは対応するbitが0で埋まります。
また、何も初期値を指定しないときも全てのbitが0になるという仕様だそうです。
初期化のNGケース
NGケース1
あまり格好よくはないですが、memsetで無理やりbitをセットします。
__vm mask;
memset(&mask, 0xFF, sizeof(__vm));
__vm mask2;
memset(&mask2, 0xFF, sizeof(__vm));
コンパイルは通りますが、実行時に次のエラーが出ます。
Bus error (core dumped) ./a.out
マスク変数の実態はレジスタで、そこにmemsetを行ったのがNGということなんですかね。
NGケース2: 関数の途中で初期化するとコンパイルエラーになることがある
以下のコードはコンパイルが通りません。
int main(){
...何かの処理...
for(int i = 0; i < N; ++i){
... 何かの処理 ...
}
//defenition of mask type mask//
__vm mask = {0};
int num_bits = __builtin_ve_pcvm(mask);
printf("num. stand bit = %d\n", num_bits);
どうやら、別の命令(特に中括弧{ }
、ここではfor
文)の次の行に__vm
型の変数定義を初期値指定して書くと
Fatal Internal Error: Please Report.: illegal token
というメッセージでコンパイルが失敗します。これはバグの可能性もありますが、現状ではバッドケースとして使わない方が良いでしょう。
とはいえ、将来改善されたら、ここの記載も修正したいと思います。
Example
c++っぽく書けると期待すると、次のように初期化子を使ってみたらどうでしょう。以下のexampleは勘で書いたソースですが、無事にコンパイル通りました。
#include <cstdio>
//the definition of vector type. 2048 means 8 byte x 256 //
typedef double __vr __attribute__((__vector_size__(2048)));
int main(){
printf("size of __vr = %d\n", (int)sizeof(__vr));
printf("size of __vm = %d\n", (int)sizeof(__vm));
//defenition of mask type mask//
__vm mask{0};
int num_bits = __builtin_ve_pcvm(mask);
printf("num. stand bit = %d\n", num_bits);
__vm mask2{1};
num_bits = __builtin_ve_pcvm(mask2);
printf("num. stand bit (2) = %d\n", num_bits);
__vm mask3{0xFF};
num_bits = __builtin_ve_pcvm(mask3);
printf("num. stand bit (3) = %d\n", num_bits);
__vm mask4{0xFFFFFFFFFFFFFFFF};
num_bits = __builtin_ve_pcvm(mask4);
printf("num. stand bit (4) = %d\n", num_bits);
__vm mask5{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF};
num_bits = __builtin_ve_pcvm(mask5);
printf("num. stand bit (5) = %d\n", num_bits);
__vm mask6{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF};
num_bits = __builtin_ve_pcvm(mask6);
printf("num. stand bit (6) = %d\n", num_bits);
return 0;
}
結果は次のような出力になりました。
size of __vr = 2048
size of __vm = 32
num. stand bit = 0
num. stand bit (2) = 1
num. stand bit (3) = 8
num. stand bit (4) = 64
num. stand bit (5) = 128
num. stand bit (6) = 256
いいですね!
32byteのマスク型変数の初期値は、8byte幅の16進数表記×4要素の配列として初期化できました。立っているビット数が0のものから256のものまで狙い通りに作れましたね。
結果
マスク型の変数を全てのbitが寝ている状態で初期化する場合は次のようにする
__vm mask;
__builtin_ve_xorm(mask, mask, mask);
マスク型の変数を全てのbitが立っている状態で初期化する場合次のようにする
__vm mask;
__builtin_ve_eqvm(mask, mask, mask);
任意の値で初期化する
//任意の値で初期化する(64bit 符号無し整数×4)
__vm mask = {0x12345678, 1, 2, 3};
おわりに
今回はマスク大尉の様に悔しがることもなく順調に行けましたね。
メリークリスマス!
と一度は書いて投稿しましたが、その後にFatal errorでした(NGケース2)、、、
マスク大尉「ベルリーーーーー!!」