はじめに
今回はマスクです。256要素のベクトル演算に対し、演算するかどうかの有効無効を各要素ごとに指定できます。それがマスクです。
- 仕様書を色々読むと、256bitのマスクレジスタがある
- intrinsicでは
__vm256というマスク型の変数を使う
とありますが、当然のように__vm256なんて型は標準ではありませんし、コンパイルしても使えません。
定義のヘッダーファイルもありません!
定義がwebのintrinsicチュートリアルにもありません!
マニュアル何てものもありません!
ベクトル変数__vrの真似をして色々書いてみても定義が間違っているといってコンパイル通りません。
マスク大尉「ベルリーーーー!!!!!!!」
ですよほんとに。(Gレコ面白いから見てみてください。ホント面白いから。)
見つけた
webのtutrial等では頻繁に登場するvelintrin.hという謎のインクルードファイル。はい当然コンパイラ4.0.0というオフィシャルには入っていませんが、これを見つけられれば__vm256の定義が書いてありそうです。
次のサイトに見つかりました
https://clang.llvm.org/doxygen/velintrin_8h_source.html
ここによれば、マスク型__vm256の定義は次らしい
※オチまで読んでください。これは通りません
// Vector mask registers
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
// For C99
typedef _Bool __vm __attribute__((ext_vector_type(256)));
typedef _Bool __vm256 __attribute__((ext_vector_type(256)));
typedef _Bool __vm512 __attribute__((ext_vector_type(512)));
#else
#ifdef __cplusplus
// For C++
typedef bool __vm __attribute__((ext_vector_type(256)));
typedef bool __vm256 __attribute__((ext_vector_type(256)));
typedef bool __vm512 __attribute__((ext_vector_type(512)));
#else
#error need C++ or C99 to use vector intrinsics for VE
#endif
#endif
こんなもん勘でどれだけ試行錯誤してもたどり着けんわ。ext_vector_typeって魔法の命令知らないと定義できないじゃん。
それで上記の定義をソースに書いてコンパイルして見ると、エラーメッセージが
$ nc++ -std=c++17 test2_mask.cpp
"test2_mask.cpp", line 16: error: the "ext_vector_type" attribute applies only
to integer or floating-point types
typedef bool __vm __attribute__((ext_vector_type(256)));
^
"test2_mask.cpp", line 16: error: invalid redeclaration of type name "__vm"
typedef bool __vm __attribute__((ext_vector_type(256)));
^
"test2_mask.cpp", line 17: error: the "ext_vector_type" attribute applies only
to integer or floating-point types
typedef bool __vm256 __attribute__((ext_vector_type(256)));
^
"test2_mask.cpp", line 18: error: the "ext_vector_type" attribute applies only
to integer or floating-point types
typedef bool __vm512 __attribute__((ext_vector_type(512)));
^
4 errors detected in the compilation of "test2_mask.cpp".
コンパイル通らないんですよね。そう、件のext_vector_typeはintかdoubleに使えというんですね、boolには使うなと。
マスク大尉「ベルリーーーーーーー!!!」
ただ、定義は__vm``__vm256``__vm512の3件なのに、よく見るとエラーメッセージは4件です。その差は、
error: invalid redeclaration of type name "__vm"
え?もしかして__vmはデフォルトで定義されてるの??
コンパイルしてみる
int main(){
__vm a;
return 0;
}
通った!!
さらに、velintrin.hを信じれば__vmと__vm256は同じサイズです。ということで、
結論:マスク型としては__vmを使いましょう。
マスクを使った演算
前回のロードとストアのテストにマスク処理を加えます。元の配列に-1.0か1.0のどちらかをセットしベクトル型aに詰めます。その値が正の時だけmaskビットを立たせます。そして、aから0で埋めたバッファbに書き戻しますが、このときにマスクが立っているものだけ書き戻します。ソースは次になります。
#include <cstdio>
//the definition of vector type. 2048 means 8 byte x 256 //
typedef double __vr __attribute__((__vector_size__(2048)));
//the definition of mask type. but the following definitions cannot be compiled//
//please use __vm without user definition, which is defined by default.
//typedef bool __vm __attribute__((ext_vector_type(256)));
//typedef bool __vm256 __attribute__((ext_vector_type(256)));
//typedef bool __vm512 __attribute__((ext_vector_type(512)));
enum class _compare :int{
_EQ, /* src == 0 */
_NE, /* src != 0 */
_GT, /* src > 0 */
_GE, /* src >= 0 */
_LT, /* src < 0 */
_LE, /* src <= 0 */
_EQNAN, /* src == 0 or src == NaN */
_NENAN, /* src != 0 or src == NaN */
_GTNAN, /* src > 0 or src == NaN */
_GENAN, /* src >= 0 or src == NaN */
_LTNAN, /* src < 0 or src == NaN */
_LENAN, /* src <= 0 or src == NaN */
_NAN, /* src == NaN */
_NUM /* src != NaN */
};
int main(){
constexpr int n = 256;
double b[n]={0.0};
for (int i = 0; i < n; ++i) {
b[i] = (double)((i%2)*2) - 1.0;
}
//definition of vector type variable a//
__vr a;
//load to vector type a from double array b//
__builtin_ve_vld(a, b, 8);
//defenition of mask type mask//
__vm mask;
//stand mask bit if a > 0//
__builtin_ve_vfmk(mask, a, (int)(_compare::_GT));
// clear b
for (int i = 0; i < n; ++i) {
b[i] = 0.0;
}
//set from vector type a to double array b//
__builtin_ve_vst(a, b, 8, mask);
//if the vector opeartion is correctly used, the numbers of 0.0 and 1.0 are repeated.//
for (int i = 0; i < n; ++i) {
printf("b[%d] = %f\n", i, b[i]);
}
return 0;
}
上手く動作すると、0.0と1.0が繰り返し表示されます。
あらためて、__vmはユーザーで定義しなくても使えます。
enum class _compare :intのところは、比較操作の種類をしているする定数です。_vector.hとenumの書き方だけ変えていますが、順番は変えていません。ベクトル命令の方は結局整数で指定して欲しいようなので、順番を変えると整数値も変わってしまいます。
次の部分でマスク型の変数maskを定義しています。
//defenition of mask type mask//
__vm mask;
次の部分で、ベクトル変数aの各要素に対して比較演算を行い、要素が0より大きいならmaskの該当bitを立てます。
//stand mask bit if a > 0//
__builtin_ve_vfmk(mask, a, (int)(_compare::_GT));
ここで_GT以外の演算を指定すれば、比較条件を変更できます。
最後に、次の部分でベクトル変数aから通常の配列bに値を書き戻します。
//set from vector type a to double array b//
__builtin_ve_vst(a, b, 8, mask);
このとき、命令__builtin_ve_vst自体は前回のストア命令と同じですが、今回は第四引数にmask変数をセットしています。これにより、マスクのbitが立っている要素だけで書き込みが行われます。
これでベクトル変数の比較からマスクを作れますね。
結論
マスク型としては__vmを使いましょう。
おわりに
定義はしておいて欲しいよね。マニュアル欲しいよね。
マスク大尉「ベルリーーーーーー!!」
Gレコ面白いから見てね。あ、でもクリスマスだからポケットの中の戦争かな。
メリークリスマス!