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では、n
とty
はそれぞれ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 つの方法で表現できます。
スケーラブルおよび固定長のベクトル型に対する通常の命令
%c = add <vscale x 4 x i32> %a, %b %f = add <4 x i32> %d, %e
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
ポリシービットのオペランドも提供します。有効な型はスケーラブルベクトル型のみです。
-
%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 し、スケーラブルな「コンテナ」型で実行する必要があります。
固定長ベクトルオペランドは、
insert_subvector
ノードを使用してスケーラブルコンテナに挿入されます。 コンテナ型は、その最小サイズが固定長ベクトルに収まるように選択されます (getContainerForFixedLengthVector
を参照)。その後、操作は **VL (ベクトル長) ノード** を介してコンテナ型で実行されます。 これらは、
RISCVInstrInfoVVLPatterns.td
で定義されているカスタムノードであり、ターゲットに依存しない SelectionDAG ノードと、いくつかの RVV 命令を反映しています。 これらには AVL オペランドが含まれており、これは固定長ベクトルの要素数に設定されます。 一部のノードにはパススルーまたはマスクオペランドもあり、通常は固定長ベクトルの Lowering時にundef
とすべて 1 に設定されます。結果は、
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_subvector
と extract_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
に設定されます.
命令選択¶
vl
と vtype
は正しく構成する必要があるため、基になるベクトル 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つのレジスタクラスがあります。
ベクトルレジスタ(
v0
、v1
、…、v32
)用のVR
。\(\text{LMUL} \leq 1\) およびマスクレジスタに使用されます。長さ2のベクトルグループ、つまり \(\text{LMUL}=2\)(
v0m2
、v2m2
、…、v30m2
)用のVRM2
。長さ4のベクトルグループ、つまり \(\text{LMUL}=4\)(
v0m4
、v4m4
、…、v28m4
)用のVRM4
。長さ8のベクトルグループ、つまり \(\text{LMUL}=8\)(
v0m8
、v8m8
、…、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