RISC-V ベクトル拡張

RISC-Vターゲットは、RISC-V ベクトル拡張 (RVV)のバージョン1.0をサポートしています。このガイドでは、LLVM IRでどのようにモデル化され、バックエンドがどのようにコードを生成するかの概要を説明します。

LLVM IR型へのマッピング

RVVは、コンパイラにとって未知の定数であるVLENサイズのレジスタを32個追加します。VLENサイズの値を表現できるようにするために、RISC-VバックエンドはAArch64のSVEと同じアプローチを取り、スケーラブルベクトル型を使用します。

スケーラブルベクトル型は、<vscale x n x ty>の形式で、型tyの要素をn個の倍数を持つベクトルを示します。RISC-Vでは、ntyはそれぞれLMULとSEWを制御します。

LLVMはELEN=32またはELEN=64のみをサポートしているため、vscaleはVLEN/64として定義されます(RISCV::RVVBitsPerBlockを参照)。これは、VLENが少なくとも64でなければならないことを意味するため、VLEN=32は現在サポートされていません。

LMUL=⅛

LMUL=¼

LMUL=½

LMUL=1

LMUL=2

LMUL=4

LMUL=8

i64 (ELEN=64)

N/A

N/A

N/A

<v x 1 x i64>

<v x 2 x i64>

<v x 4 x i64>

<v x 8 x i64>

i32

N/A

N/A

<v x 1 x i32>

<v x 2 x i32>

<v x 4 x i32>

<v x 8 x i32>

<v x 16 x i32>

i16

N/A

<v x 1 x i16>

<v x 2 x i16>

<v x 4 x i16>

<v x 8 x i16>

<v x 16 x i16>

<v x 32 x i16>

i8

<v x 1 x i8>

<v x 2 x i8>

<v x 4 x i8>

<v x 8 x i8>

<v x 16 x i8>

<v x 32 x i8>

<v x 64 x i8>

double (ELEN=64)

N/A

N/A

N/A

<v x 1 x double>

<v x 2 x double>

<v x 4 x double>

<v x 8 x double>

float

N/A

N/A

<v x 1 x float>

<v x 2 x float>

<v x 4 x float>

<v x 8 x float>

<v x 16 x float>

half

N/A

<v x 1 x half>

<v x 2 x half>

<v x 4 x half>

<v x 8 x half>

<v x 16 x half>

<v x 32 x half>

(<v x k x ty><vscale x k x ty> として読む)

マスクベクトル型

マスクベクトルは、ベクトルレジスタ内の密にパックされたビットのレイアウトを使用して物理的に表現されます。 これらは、以下の LLVM IR 型にマッピングされます。

  • <vscale x 1 x i1>

  • <vscale x 2 x i1>

  • <vscale x 4 x i1>

  • <vscale x 8 x i1>

  • <vscale x 16 x i1>

  • <vscale x 32 x i1>

  • <vscale x 64 x i1>

同じ SEW/LMUL 比を持つ 2 つの型は、同じ関連マスク型を持ちます。 たとえば、SEW=64、LMUL=2 の比較と SEW=32、LMUL=1 の比較は、どちらもマスク <vscale x 2 x i1> を生成します.

LLVM IR での表現

ベクトル命令は、LLVM IR では主に 3 つの方法で表現できます。

  1. スケーラブルおよび固定長のベクトル型に対する通常の命令

    %c = add <vscale x 4 x i32> %a, %b
    %f = add <4 x i32> %d, %e
    
  2. C イントリンシック仕様を反映した RISC-V ベクトルイントリンシック

    これらはマスクされていないバリアントで提供されます

    %c = call @llvm.riscv.vadd.nxv4i32.nxv4i32(
           <vscale x 4 x i32> %passthru,
           <vscale x 4 x i32> %a,
           <vscale x 4 x i32> %b,
           i64 %avl
         )
    

    また、マスクされたバリアントも提供されます

    %c = call @llvm.riscv.vadd.mask.nxv4i32.nxv4i32(
           <vscale x 4 x i32> %passthru,
           <vscale x 4 x i32> %a,
           <vscale x 4 x i32> %b,
           <vscale x 4 x i1> %mask,
           i64 %avl,
           i64 0 ; policy (must be an immediate)
         )
    

    どちらも AVL の設定と、パススルーオペランドを介した非アクティブ/テール要素の制御を可能にしますが、マスクされたバリアントは、マスクと vta/vma ポリシービットのオペランドも提供します。

    有効な型はスケーラブルベクトル型のみです。

  3. ベクトル述語 (VP) イントリンシック

    %c = call @llvm.vp.add.nxv4i32(
           <vscale x 4 x i32> %a,
           <vscale x 4 x i32> %b,
           <vscale x 4 x i1> %m
           i32 %evl
         )
    

    RISC-V イントリンシックとは異なり、VP イントリンシックはターゲットに依存しないため、中間エンドの他の最適化パス (ループベクトル化など) から出力できます。 また、固定長のベクトル型もサポートしています。

    VPイントリンシックにもパススルーオペランドはありませんが、テール/マスクの未変更の動作は、@llvm.vp.merge で出力をしようすることでエミュレートできます。 vmerge として Lowering されますが、RISCVDAGToDAGISel::performCombineVMergeAndVOps によって、基になる命令のマスクにマージされます。

上記の表現のさまざまなプロパティを以下にまとめます。

AVL

マスキング

パススルー

スケーラブルベクトル

固定長ベクトル

ターゲット非依存

LLVM IR 命令

常に VLMAX

いいえ

なし

はい

はい

はい

RVV イントリンシック

はい

はい

はい

はい

いいえ

いいえ

VP イントリンシック

はい (EVL)

はい

いいえ

はい

はい

はい

SelectionDAG Lowering

ほとんどの通常の **スケーラブル** ベクトル LLVM IR 命令では、対応する SelectionDAG ノードは RISC-V で正当であり、カスタム Lowering は必要ありません。

t5: nxv4i32 = add t2, t4

RISC-V ベクトルイントリンシックもカスタム Lowering は必要ありません。

t12: nxv4i32 = llvm.riscv.vadd TargetConstant:i64<10056>, undef:nxv4i32, t2, t4, t6

固定長ベクトル

固定長ベクトルパターンがないため、固定長ベクトルはカスタム Lowering し、スケーラブルな「コンテナ」型で実行する必要があります。

  1. 固定長ベクトルオペランドは、insert_subvector ノードを使用してスケーラブルコンテナに挿入されます。 コンテナ型は、その最小サイズが固定長ベクトルに収まるように選択されます (getContainerForFixedLengthVector を参照)。

  2. その後、操作は **VL (ベクトル長) ノード** を介してコンテナ型で実行されます。 これらは、RISCVInstrInfoVVLPatterns.td で定義されているカスタムノードであり、ターゲットに依存しない SelectionDAG ノードと、いくつかの RVV 命令を反映しています。 これらには AVL オペランドが含まれており、これは固定長ベクトルの要素数に設定されます。 一部のノードにはパススルーまたはマスクオペランドもあり、通常は固定長ベクトルの Lowering時に undef とすべて 1 に設定されます。

  3. 結果は、extract_subvector によって固定長ベクトルに戻されます。

    t2: nxv2i32,ch = CopyFromReg t0, Register:nxv2i32 %0
    t6: nxv2i32,ch = CopyFromReg t0, Register:nxv2i32 %1
  t4: v4i32 = extract_subvector t2, Constant:i64<0>
  t7: v4i32 = extract_subvector t6, Constant:i64<0>
t8: v4i32 = add t4, t7

// is custom lowered to:

    t2: nxv2i32,ch = CopyFromReg t0, Register:nxv2i32 %0
    t6: nxv2i32,ch = CopyFromReg t0, Register:nxv2i32 %1
    t15: nxv2i1 = RISCVISD::VMSET_VL Constant:i64<4>
  t16: nxv2i32 = RISCVISD::ADD_VL t2, t6, undef:nxv2i32, t15, Constant:i64<4>
t17: v4i32 = extract_subvector t16, Constant:i64<0>

VL ノードには、多くの場合、パススルーまたはマスクオペランドがあり、通常は固定長ベクトルの場合は undef とすべて 1 に設定されます。

ラッピングとアンラッピングを行う insert_subvectorextract_subvector ノードは結合され、最終的にはすべての固定長ベクトル型をスケーラブルに Lowering します。 関数のインターフェースにおける固定長ベクトルは、スケーラブルベクトルコンテナで渡されることに注意してください。

注記

Lowering をパススルーする insert_subvector and extract_subvector ノードは、正確なサブレジスタの挿入または抽出として実行できるもののみです。 これは、正当化されていない固定長ベクトル insert_subvector and extract_subvector ノードがレジスタグループの境界上にある必要があることを意味するため、正確な VLEN はコンパイル時に既知でなければなりません (つまり、-mrvv-vector-bits=zvl または -mllvm -riscv-v-vector-bits-max=VLEN でコンパイルするか、正確な vscale_range 属性を持つ必要があります)。

ベクトル述語イントリンシック

VP イントリンシックも VL ノードを介してカスタム Lowering されます。

t12: nxv2i32 = vp_add t2, t4, t6, Constant:i64<8>

// is custom lowered to:

t18: nxv2i32 = RISCVISD::ADD_VL t2, t4, undef:nxv2i32, t6, Constant:i64<8>

VP EVL とマスクは、それぞれ VL ノードの AVL とマスクに使用され、パススルーは undef に設定されます.

命令選択

vlvtype は正しく構成する必要があるため、基になるベクトル MachineInstr を直接選択することはできません。 代わりに、必要な vsetvli を後で出力するために必要な追加情報を伝える疑似命令が選択されます。

%c:vrm2 = PseudoVADD_VV_M2 %passthru:vrm2(tied-def 0), %a:vrm2, %b:vrm2, %vl:gpr, 5 /*sew*/, 3 /*policy*/

各ベクトル命令は、RISCVInstrInfoVPseudos.td で定義された複数の擬命令を持ちます。それぞれの擬命令は、可能なLMULごとにバリアントがあり、さらにマスクされたバリアントもあります。そのため、vadd.vv のような典型的な命令には、以下の擬命令が存在します。

%rd:vr = PseudoVADD_VV_MF8 %passthru:vr(tied-def 0), %rs2:vr, %rs1:vr, %avl:gpr, sew:imm, policy:imm
%rd:vr = PseudoVADD_VV_MF4 %passthru:vr(tied-def 0), %rs2:vr, %rs1:vr, %avl:gpr, sew:imm, policy:imm
%rd:vr = PseudoVADD_VV_MF2 %passthru:vr(tied-def 0), %rs2:vr, %rs1:vr, %avl:gpr, sew:imm, policy:imm
%rd:vr = PseudoVADD_VV_M1 %passthru:vr(tied-def 0), %rs2:vr, %rs1:vr, %avl:gpr, sew:imm, policy:imm
%rd:vrm2 = PseudoVADD_VV_M2 %passthru:vrm2(tied-def 0), %rs2:vrm2, %rs1:vrm2, %avl:gpr, sew:imm, policy:imm
%rd:vrm4 = PseudoVADD_VV_M4 %passthru:vrm4(tied-def 0), %rs2:vrm4, %rs1:vrm4, %avl:gpr, sew:imm, policy:imm
%rd:vrm8 = PseudoVADD_VV_M8 %passthru:vrm8(tied-def 0), %rs2:vrm8, %rs1:vrm8, %avl:gpr, sew:imm, policy:imm
%rd:vr = PseudoVADD_VV_MF8_MASK %passthru:vr(tied-def 0), %rs2:vr, %rs1:vr, mask:$v0, %avl:gpr, sew:imm, policy:imm
%rd:vr = PseudoVADD_VV_MF4_MASK %passthru:vr(tied-def 0), %rs2:vr, %rs1:vr, mask:$v0, %avl:gpr, sew:imm, policy:imm
%rd:vr = PseudoVADD_VV_MF2_MASK %passthru:vr(tied-def 0), %rs2:vr, %rs1:vr, mask:$v0, %avl:gpr, sew:imm, policy:imm
%rd:vr = PseudoVADD_VV_M1_MASK %passthru:vr(tied-def 0), %rs2:vr, %rs1:vr, mask:$v0, %avl:gpr, sew:imm, policy:imm
%rd:vrm2 = PseudoVADD_VV_M2_MASK %passthru:vrm2(tied-def 0), %rs2:vrm2, %%rs1:vrm2, mask:$v0, %avl:gpr, sew:imm, policy:imm
%rd:vrm4 = PseudoVADD_VV_M4_MASK %passthru:vrm4(tied-def 0), %rs2:vrm4, %rs1:vrm4, mask:$v0, %avl:gpr, sew:imm, policy:imm
%rd:vrm8 = PseudoVADD_VV_M8_MASK %passthru:vrm8(tied-def 0), %rs2:vrm8, %rs1:vrm8, mask:$v0, %avl:gpr, sew:imm, policy:imm

注記

SEWはオペランドでエンコードできますが、異なるレジスタグループは異なるレジスタクラスを必要とするため、LMULごとに個別の擬命令を使用する必要があります:レジスタ割り当てを参照してください。

擬命令は、AVLとSEW(2の累乗としてエンコードされる)のオペランドを持ち、さらに該当する場合はマスク、ポリシー、または丸めモードのオペランドを持つ場合があります。パススルーオペランドは、非アクティブ/テール要素を決定するデスティネーションレジスタに関連付けられています。

VLMAXを使用するスケーラブルベクトルでは、AVLはセンチネル値 -1 に設定されます。

ターゲットに依存しないSelectionDAGノードのパターンは RISCVInstrInfoVSDPatterns.td に、VLノードは RISCVInstrInfoVVLPatterns.td に、RVV組み込み関数は RISCVInstrInfoVPseudos.td にあります。

マスクパターン

マスクされた擬命令の場合、マスクオペランドは命令選択中に、接着された CopyToReg ノードを使用して物理 $v0 レジスタにコピーされます。

  t23: ch,glue = CopyToReg t0, Register:nxv4i1 $v0, t6
t25: nxv4i32 = PseudoVADD_VV_M2_MASK Register:nxv4i32 $noreg, t2, t4, Register:nxv4i1 $v0, TargetConstant:i64<8>, TargetConstant:i64<5>, TargetConstant:i64<1>, t23:1

RISCVInstrInfoVVLPatterns.td のパターンは、一致テーブルのサイズを小さくするために、ノードのマスクがすべて1でマスクされていない擬命令になる可能性がある場合でも、マスクされた擬命令のみと一致します。 RISCVFoldMasks::convertToUnmasked は、マスクがすべて1であるかどうかを検出し、マスクされていない形式に変換します。

$v0 = PseudoVMSET_M_B16 -1, 32
%rd:vrm2 = PseudoVADD_VV_M2_MASK %passthru:vrm2(tied-def 0), %rs2:vrm2, %rs1:vrm2, $v0, %avl:gpr, sew:imm, policy:imm

// gets optimized to:

%rd:vrm2 = PseudoVADD_VV_M2 %passthru:vrm2(tied-def 0), %rs2:vrm2, %rs1:vrm2, %avl:gpr, sew:imm, policy:imm

注記

AVLを超えるテール要素は undef であり、1に置き換えることができるため、vmset.m はすべて1のマスクとして扱うことができます。

レジスタ割り当て

レジスタ割り当ては、ベクトルレジスタとスカラーレジスタに分割され、ベクトル割り当てが最初に実行されます。

$v8m2 = PseudoVADD_VV_M2 $v8m2(tied-def 0), $v8m2, $v10m2, %vl:gpr, 5, 3

注記

レジスタ割り当ては、RISCVInsertVSETVLI がベクトルレジスタ割り当て後、スカラーレジスタ割り当て前に実行できるように分割されます。 AVLをVLMAXに設定するために新しい仮想レジスタを作成する必要がある場合があるため、スカラーレジスタ割り当ての前に実行する必要があります。

ベクトルレジスタ割り当て後に RISCVInsertVSETVLI を実行すると、マシン スケジューラに対する制約が少なくなります。これは、vsetvli の後に命令をスケジュールできないためです。また、スピリングまたは定数再物質化中にさらにベクトル擬命令を発行することができます。

ベクトルには4つのレジスタクラスがあります。

  • ベクトルレジスタ(v0v1、…、v32)用の VR。\(\text{LMUL} \leq 1\) およびマスクレジスタに使用されます。

  • 長さ2のベクトルグループ、つまり \(\text{LMUL}=2\)(v0m2v2m2、…、v30m2)用の VRM2

  • 長さ4のベクトルグループ、つまり \(\text{LMUL}=4\)(v0m4v4m4、…、v28m4)用の VRM4

  • 長さ8のベクトルグループ、つまり \(\text{LMUL}=8\)(v0m8v8m8、…、v24m8)用の VRM8

\(\text{LMUL} \lt 1\) タイプとマスクタイプは、専用のクラスを持つことによる利点がないため、これらの場合は VR が使用されます。

一部の命令には、レジスタオペランドが V0 であってはならない、または V0 と重複してはならないという制約があるため、これらの場合に備えて VRNoV0 バリアントも用意されています。

RISCVInsertVSETVLI

ベクトルレジスタが割り当てられた後、RISCVInsertVSETVLI パスは擬命令に必要な vsetvli を挿入します。

dead $x0 = PseudoVSETVLI %vl:gpr, 209, implicit-def $vl, implicit-def $vtype
$v8m2 = PseudoVADD_VV_M2 $v8m2(tied-def 0), $v8m2, $v10m2, $noreg, 5, implicit $vl, implicit $vtype

物理 $vl レジスタと $vtype レジスタは、PseudoVSETVLI によって暗黙的に定義され、PseudoVADD によって暗黙的に使用されます。 vtype オペランド(この例では 209)は、RISCVVType::encodeVTYPE を介して仕様に従ってエンコードされます。

RISCVInsertVSETVLI は、データフロー分析を実行して、可能な限り少ない vsetvli を発行します。また、VLを設定する vsetvli の数を最小限に抑えようとします。つまり、vtype のみを変更する必要があるが、vl は変更する必要がない場合は、vsetvli x0, x0 を発行します。

擬命令の展開と出力

スカラーレジスタ割り当て後、RISCVExpandPseudoInsts.cpp パスは PseudoVSETVLI 命令を展開します。

dead $x0 = VSETVLI $x1, 209, implicit-def $vtype, implicit-def $vl
renamable $v8m2 = PseudoVADD_VV_M2 $v8m2(tied-def 0), $v8m2, $v10m2, $noreg, 5, implicit $vl, implicit $vtype

LMULのレジスタクラスをエンコードするために必要なため、ベクトル擬命令は残ります。そのAVLおよびSEWオペランドは、もはや使用されません。

RISCVAsmPrinter は、擬命令を実際の MCInst に変換します。

vsetvli a0, zero, e32, m2, ta, ma
vadd.vv v8, v8, v10

関連項目