RISC-V Vector Intrinsicのドキュメントでは、サンプルアプリケーションがいくつか格納されている。
https://github.com/riscv-non-isa/rvv-intrinsic-doc/tree/master/examples
それぞれのアプリケーションにおいて、どのようにベクトル命令が使用されているのかを見てみることにした。
rvv_strcpy
char *strcpy_vec(char *dst, const char *src) { char *save = dst; size_t vlmax = vsetvlmax_e8m8(); long first_set_bit = -1; size_t vl; while (first_set_bit < 0) { vint8m8_t vec_src = vle8ff_v_i8m8(src, &vl, vlmax); vbool1_t string_terminate = vmseq_vx_i8m8_b1(vec_src, 0, vl); vbool1_t mask = vmsif_m_b1(string_terminate, vl); vse8_v_i8m8_m(mask, dst, vec_src, vl); src += vl; dst += vl; first_set_bit = vfirst_m_b1(string_terminate, vl); } return save; }
10184: 041076d7 vsetvli a3,zero,e8,m2,ta,mu 10188: 03070d07 vle8ff.v v26,(a4) 1018c: 03078e07 vle8ff.v v28,(a5) 10190: 63a03c57 vmseq.vi v24,v26,0 10194: 67ae0d57 vmsne.vv v26,v26,v28 10198: 6b8d2c57 vmor.mm v24,v24,v26 1019c: 4388a657 vfirst.m a2,v24 101a0: c20026f3 csrr a3,vl 101a4: fc064ee3 bltz a2,10180 <main+0x7a>
vsetvli
でレジスタ幅を最大に設定して読み込みデータ量を最大にする。vle8ff.v
で文字列をロードする。ffを使用しているのは、アクセス禁止領域まではみ出した場合の処理を考慮するため。vmseq.vi 0
により、最初に0(つまり文字列の最後)である場所にマークを付ける。vmsif.m
により、マークを付けたレジスタの下位のビットをすべて1に設定する(文字列の途中だと、基本的にすべて1になるはず)。この結果をv0に格納する。vse8.v
により、マークを付けた場所のみストアする- 処理したバイト数だけポインタを進め、残り文字数
count
を減算する - vfirst.mにより、文字列の最後が検出されたかどうかをチェックする。文字列の最後が検出された場合、ループを抜ける。
rvv_strncpy
char *strncpy_vec(char *dst, char *src, size_t count) { char *save = dst; size_t new_vl; long first_set_bit = -1; while (first_set_bit < 0) { if (count == 0) return save; size_t vl = vsetvl_e8m1(count); vint8m1_t vec_src = vle8ff_v_i8m1(src, &new_vl, vl); vbool8_t string_terminate = vmseq_vx_i8m1_b8(vec_src, 0, new_vl); vbool8_t mask = vmsif_m_b8(string_terminate, new_vl); vse8_v_i8m1_m(mask, dst, vec_src, new_vl); count -= new_vl; src += new_vl; dst += new_vl; first_set_bit = vfirst_m_b8(string_terminate, new_vl); } size_t tail = new_vl - first_set_bit; count += tail; dst -= tail; size_t vlmax = vsetvlmax_e8m1(); vint8m1_t vec_zero = vmv_v_x_i8m1(0, vlmax); do { size_t vl = vsetvl_e8m1(count); vse8_v_i8m1(dst, vec_zero, vl); count -= vl; dst += vl; } while (count > 0); return save; }
1017e: 0c37f057 vsetvli zero,a5,e8,m8,ta,ma 10182: 03058c07 vle8ff.v v24,(a1) 10186: 63803457 vmseq.vi v8,v24,0 1018a: 5281a057 vmsif.m v0,v8 1018e: 00070c27 vse8.v v24,(a4),v0.t 10192: c2002673 csrr a2,vl 10196: 4288a557 vfirst.m a0,v8
基本的にrvv_strcpyと似ているが、count
よりもコピーした文字列が小さかった場合、残りを\0
で埋める。
rvv_strcmp
int strcmp_vec(const char *src1, const char *src2) { size_t vlmax = vsetvlmax_e8m2(); long first_set_bit = -1; size_t vl, vl1; while (first_set_bit < 0) { vint8m2_t vec_src1 = vle8ff_v_i8m2(src1, &vl, vlmax); vint8m2_t vec_src2 = vle8ff_v_i8m2(src2, &vl1, vlmax); vbool4_t string_terminate = vmseq_vx_i8m2_b4(vec_src1, 0, vl); vbool4_t no_equal = vmsne_vv_i8m2_b4(vec_src1, vec_src2, vl); vbool4_t vec_terminate = vmor_mm_b4(string_terminate, no_equal, vl); first_set_bit = vfirst_m_b4(vec_terminate, vl); src1 += vl; src2 += vl; } src1 -= vl - first_set_bit; src2 -= vl - first_set_bit; return *src1 - *src2; }
106ae: 04107657 vsetvli a2,zero,e8,m2,ta,mu 106b2: 04167057 vsetvli zero,a2,e8,m2,ta,mu 106b6: 03050c07 vle8ff.v v24,(a0) 106ba: 04167057 vsetvli zero,a2,e8,m2,ta,mu 106be: 03058e07 vle8ff.v v28,(a1) 106c2: c20027f3 csrr a5,vl 106c6: 88aa mv a7,a0 106c8: 0417f057 vsetvli zero,a5,e8,m2,ta,mu 106cc: 882e mv a6,a1 106ce: 63803d57 vmseq.vi v26,v24,0 106d2: 953e add a0,a0,a5 106d4: 678e0c57 vmsne.vv v24,v24,v28 106d8: 95be add a1,a1,a5 106da: 6bac2c57 vmor.mm v24,v26,v24 106de: 4388a6d7 vfirst.m a3,v24 106e2: fc06c8e3 bltz a3,106b2 <strcmp_vec+0x4>
srcとdstを比較する。
vle8ff.v
により、v24/v25に文字列をロードするvle8ff.v
により、v28/v29に文字列をロードするvmseq.vi
により、v24/v25で0になっている場所をマークし、v26に格納する (これが文字列の終端のマークになる)vmsne.v
により、ロードした文字列同士を比較して結果をv24に格納するvmor.mm
により、マーク同士をORし、文字列の最終か、不一致の場所をマークする- マークした場所に基づいて
vfirst.m
を用いてインデックスを計算する
rvv_branch
void branch(double *a, double *b, double *c, int n, double constant) { // set vlmax and initialize variables size_t vlmax = vsetvlmax_e64m1(); vfloat64m1_t vec_zero = vfmv_v_f_f64m1(0, vlmax); vfloat64m1_t vec_constant = vfmv_v_f_f64m1(constant, vlmax); for (size_t vl; n > 0; n -= vl, a += vl, b += vl, c += vl) { vl = vsetvl_e64m1(n); vfloat64m1_t vec_a = vle64_v_f64m1(a, vl); vfloat64m1_t vec_b = vle64_v_f64m1(b, vl); vbool64_t mask = vmfne_vv_f64m1_b64(vec_b, vec_zero, vl); vfloat64m1_t vec_c = vfdiv_vv_f64m1_m(mask, /*maskedoff=*/vec_constant, vec_a, vec_b, vl); vse64_v_f64m1(c, vec_c, vl); } }
倍精度浮動小数点配列a
とb
の値をロードし、b
の値が0でない要素について、要素a
と要素b
の値をそれぞれ除算し配列c
に格納する。b
の値が0の場合には、c
にconstant
の値を代わりに格納する。
10168: 05807757 vsetvli a4,zero,e64,m1,ta,mu 1016c: 5e075dd7 vfmv.v.f v27,fa4 10170: 0587f8d7 vsetvli a7,a5,e64,m1,ta,mu 10174: 9fb03cd7 vmv1r.v v25,v27 10178: 02087c07 vle64.v v24,(a6) 1017c: 411787b3 sub a5,a5,a7 10180: 7387d057 vmfne.vf v0,v24,fa5 10184: 00057d07 vle64.v v26,(a0),v0.t 10188: 81ac1cd7 vfdiv.vv v25,v26,v24,v0.t 1018c: 0206fca7 vse64.v v25,(a3) 10190: 982e add a6,a6,a1 10192: 952e add a0,a0,a1 10194: 96ae add a3,a3,a1 10196: ffe9 bnez a5,10170 <main+0x6a>
- vsetvliにより演算対象を64ビットに設定する
- vfmv.v.fにより、浮動小数点引数constantの値をベクトルレジスタに展開する
- ループ開始
- vsetvliにより、最大ベクトル長を設定する
- vle64.vによりベクトル要素bをロードする
- ベクトル要素bの値に基づいてマスクを作成する。要素が0でない場合にマスクを設定する
- vle64.vによりベクトル要素aをロードする(最適化により、aのロードする要素はマスクの情報に基づいてロードしている)
- 除算命令(vfdiv.vv)の書き込みレジスタには、あらかじめv27(constant)の値がロードしてあり、演算が行われる場合は除算の結果が書き込まれ、マスクされている場合はデフォルト値(constant)が維持されるようになっている。
- これをすべての要素に対して繰り返す
rvv_index
void index_(double *a, double *b, double *c, int n) { size_t vlmax = vsetvlmax_e32m1(); vuint32m1_t vec_i = vid_v_u32m1(vlmax); for (size_t vl; n > 0; n -= vl, a += vl, b += vl, c += vl) { vl = vsetvl_e64m2(n); vfloat64m2_t vec_i_double = vfwcvt_f_xu_v_f64m2(vec_i, vl); vfloat64m2_t vec_b = vle64_v_f64m2(b, vl); vfloat64m2_t vec_c = vle64_v_f64m2(c, vl); vfloat64m2_t vec_a = vfadd_vv_f64m2(vec_b, vfmul_vv_f64m2(vec_c, vec_i_double, vl), vl); vse64_v_f64m2(a, vec_a, vl); vec_i = vadd_vx_u32m1(vec_i, vl, vl); } }
倍精度浮動小数点の要素b/cおよび整数nを用いて以下の演算を行う。
$a[] = b[] + c[*] \times (double)c$
10188: 050077d7 vsetvli a5,zero,e32,m1,ta,mu 1018c: 5208ae57 vid.v v28 10190: 0596f7d7 vsetvli a5,a3,e64,m2,ta,mu 10194: 0507f057 vsetvli zero,a5,e32,m1,ta,mu 10198: 0007831b sext.w t1,a5 1019c: 02087c07 vle64.v v24,(a6) 101a0: 0208fd07 vle64.v v26,(a7) 101a4: 00379513 slli a0,a5,0x3 101a8: 4bc51f57 vfwcvt.f.xu.v v30,v28 101ac: 406686bb subw a3,a3,t1 101b0: 05907057 vsetvli zero,zero,e64,m2,ta,mu 101b4: 93af1d57 vfmul.vv v26,v26,v30 101b8: 038d1c57 vfadd.vv v24,v24,v26 101bc: 0205fc27 vse64.v v24,(a1) 101c0: 982a add a6,a6,a0 101c2: 05007057 vsetvli zero,zero,e32,m1,ta,mu 101c6: 95aa add a1,a1,a0 101c8: 98aa add a7,a7,a0 101ca: 03c34e57 vadd.vx v28,v28,t1 101ce: fcd041e3 bgtz a3,10190 <main+0x8a>
整数は32ビットで浮動小数点は64ビットなので、型の変換とレジスタの拡張に注意。
vid.v
によりv28に32ビットのシーケンシャルな値を展開するvsetvli
により演算の型情報を32ビットに設定している。- その後、
vle64.v
を実行してaの値をロードしているが、VSEW
が32ビットなので、レジスタ2つ分(v24/v25)に値がロードされる - 同様に
vle64.v
により、v26,v27にbの値がロードされる vfwcvt.f.xu.v
により、v28の32ビット毎のシーケンシャルの値が、v30,v31の2つのレジスタに倍精度の浮動小数点として返還されるvfmul.vv
によりv26/v27の値とv30/v31の値の乗算が行われるvfadd.vv
により上記の演算結果とv24/v25の結果を加算するvse64.v
により、v24/v25の値をストアするvsetvli
により、e32/m1でレジスタ1本の使用モードに変換するvadd.vx
により、vid.v
で指定したシーケンシャルな値をそれぞれ次の配列に備えてインクリメントする
rvv_saxpy
void saxpy_vec(size_t n, const float a, const float *x, float *y) { size_t l; vfloat32m8_t vx, vy; for (; n > 0; n -= l) { l = vsetvl_e32m8(n); vx = vle32_v_f32m8(x, l); x += l; vy = vle32_v_f32m8(y, l); vy = vfmacc_vf_f32m8(vy, a, vx, l); vse32_v_f32m8 (y, vy, l); y += l; } }
シンプルに配列xと配列y、定数aを使用して以下の演算を行う。
$y[] = y[] + x[*] * a$
1015a: 053777d7 vsetvli a5,a4,e32,m8,ta,mu 1015e: 0205e407 vle32.v v8,(a1) 10162: 0206ec07 vle32.v v24,(a3) 10166: 00279513 slli a0,a5,0x2 1016a: 0137f057 vsetvli zero,a5,e32,m8,tu,mu 1016e: 8f1d sub a4,a4,a5 10170: b287dc57 vfmacc.vf v24,fa5,v8 10174: 0206ec27 vse32.v v24,(a3) 10178: 95aa add a1,a1,a0 1017a: 96aa add a3,a3,a0 1017c: ff79 bnez a4,1015a <main+0x54>
vsetvli
によりVSEW=32, 8レジスタを使用するモードにするvle32.v
により、配列xの値をv8-v15にロードするvle32.v
により、配列yの値をv24-v31にロードするvfmacc.vf
により、v8-v15, v24-v31とfa5を用いて演算を行う- 演算結果を
vse32.v
でストアする