- Introduction
- Type System
- Configuration-Setting and
vl
Argument - Naming Rules
- Exceptions in Naming
- Scalar in Vector Operations
- Mask in Intrinsics
- Masked Intrinsics Without MaskedOff
- Policy Intrinsic Functions
- Keep the Original Values of the Destination Vector
- SEW and LMUL of Intrinsics
- C Operators on RISC-V Vector Types
- Utility Functions
- Overloaded Interface
- Switching Vtype and Keep same VL in a Loop
- Programming Note
This document introduces the intrinsics for RISC-V vector programming, including the design decision we take, the type system, the general naming rules for intrinsics, and facilities for vector users. It does not list all available intrinsics for vector programming. The full set of intrinsics will be written in another document.
In order to access the intrinsics for RISC-V vector programming, it is necessary to include the header file riscv_vector.h
.
Further, individual intrinsic functions depend on the availability of the corresponding vector instructions (as part of the effective -march
string).
Encode SEW
and LMUL
into data types. We enforce the constraint LMUL ≥ SEW/ELEN
in the implementation. There are the following data types for ELEN
= 64.
Types | LMUL = 1 | LMUL = 2 | LMUL = 4 | LMUL = 8 | LMUL = 1/2 | LMUL = 1/4 | LMUL = 1/8 |
---|---|---|---|---|---|---|---|
int64_t | vint64m1_t | vint64m2_t | vint64m4_t | vint64m8_t | N/A | N/A | N/A |
uint64_t | vuint64m1_t | vuint64m2_t | vuint64m4_t | vuint64m8_t | N/A | N/A | N/A |
int32_t | vint32m1_t | vint32m2_t | vint32m4_t | vint32m8_t | vint32mf2_t | N/A | N/A |
uint32_t | vuint32m1_t | vuint32m2_t | vuint32m4_t | vuint32m8_t | vuint32mf2_t | N/A | N/A |
int16_t | vint16m1_t | vint16m2_t | vint16m4_t | vint16m8_t | vint16mf2_t | vint16mf4_t | N/A |
uint16_t | vuint16m1_t | vuint16m2_t | vuint16m4_t | vuint16m8_t | vuint16mf2_t | vuint16mf4_t | N/A |
int8_t | vint8m1_t | vint8m2_t | vint8m4_t | vint8m8_t | vint8mf2_t | vint8mf4_t | vint8mf8_t |
uint8_t | vuint8m1_t | vuint8m2_t | vuint8m4_t | vuint8m8_t | vuint8mf2_t | vuint8mf4_t | vuint8mf8_t |
vfloat64 | vfloat64m1_t | vfloat64m2_t | vfloat64m4_t | vfloat64m8_t | N/A | N/A | N/A |
vfloat32 | vfloat32m1_t | vfloat32m2_t | vfloat32m4_t | vfloat32m8_t | vfloat32mf2_t | N/A | N/A |
vfloat16 | vfloat16m1_t | vfloat16m2_t | vfloat16m4_t | vfloat16m8_t | vfloat16mf2_t | vfloat16mf4_t | N/A |
There are the following data types for ELEN
= 32.
Types | LMUL = 1 | LMUL = 2 | LMUL = 4 | LMUL = 8 | LMUL = 1/2 | LMUL = 1/4 | LMUL = 1/8 |
---|---|---|---|---|---|---|---|
int32_t | vint32m1_t | vint32m2_t | vint32m4_t | vint32m8_t | N/A | N/A | N/A |
uint32_t | vuint32m1_t | vuint32m2_t | vuint32m4_t | vuint32m8_t | N/A | N/A | N/A |
int16_t | vint16m1_t | vint16m2_t | vint16m4_t | vint16m8_t | vint16mf2_t | N/A | N/A |
uint16_t | vuint16m1_t | vuint16m2_t | vuint16m4_t | vuint16m8_t | vuint16mf2_t | N/A | N/A |
int8_t | vint8m1_t | vint8m2_t | vint8m4_t | vint8m8_t | vint8mf2_t | vint8mf4_t | N/A |
uint8_t | vuint8m1_t | vuint8m2_t | vuint8m4_t | vuint8m8_t | vuint8mf2_t | vuint8mf4_t | N/A |
vfloat32 | vfloat32m1_t | vfloat32m2_t | vfloat32m4_t | vfloat32m8_t | N/A | N/A | N/A |
vfloat16 | vfloat16m1_t | vfloat16m2_t | vfloat16m4_t | vfloat16m8_t | vfloat16mf2_t | N/A | N/A |
Encode the ratio of SEW
/LMUL
into the mask types. There are the following mask types.
n = SEW
/LMUL
Types | n = 1 | n = 2 | n = 4 | n = 8 | n = 16 | n = 32 | n = 64 |
---|---|---|---|---|---|---|---|
bool | vbool1_t | vbool2_t | vbool4_t | vbool8_t | vbool16_t | vbool32_t | vbool64_t |
There are two variants of configuration setting intrinsics. vsetvl
is used to
get the active vector length (vl
) according to the given application vector
length(AVL
), SEW
and LMUL
.
vl
register status is not exposed to C language level, so in theory you can
treat vsetvl
as a function which returns the min value between avl
and VLMAX
and vsetvlmax
as returning VLMAX
.
size_t vsetvl_e8m1 (size_t avl);
size_t vsetvl_e8m2 (size_t avl);
size_t vsetvl_e8m4 (size_t avl);
size_t vsetvl_e8m8 (size_t avl);
size_t vsetvlmax_e8m1 ();
size_t vsetvlmax_e8m2 ();
size_t vsetvlmax_e8m4 ();
size_t vsetvlmax_e8m8 ();
SEW
and LMUL
are a part of the naming. They are static information for the
intrinsics.
All of the intrinsic functions have a vl
argument to specify the active
vector length, except a few functions which operate regardless of vl
. e.g. vmv.x.s
,
vfmv.f.s
, vundefined
, vreinterpret
, vlmul_ext
, vlmul_trunc
, vget
,
vset
and vcreate
.
The intrinsic functions will only operate at most VLMAX
elements if the vl
arguments are larger than VLMAX
.
The semantics of following two snippets are equivalent. We strongly suggest the first form.
size_t vl = vsetvl_e8m1 (avl);
vint8m1_t va, vb, vc;
va = vadd_vv_i8m1(vb, vc, vl);
vint8m1_t va, vb, vc;
va = vadd_vv_i8m1(vb, vc, avl);
Intrinsics is the interface to the low level assembly in high level programming language. The intrinsic API has the goal to make all the V-ext instructions accessible from C/C++. The intrinsic names are as close as the assembly mnemonics. Besides the basic intrinsics corresponding to assembly mnemonics, there are intrinsics close to semantic naming.
The intrinsic names will encode return type if it is appropriate. It is easier to know the output type of the intrinsics from the name. In addition, if the intrinsic call is used as the operand, having the return type is more immediate. If there is no return value, the intrinsics will encode the input value types. If the return type is the same, use exceptional rules to differentiate them. See Exceptions in Naming.
In general, the naming rule of intrinsics is
INTRINSIC ::= MNEMONIC '_' RET_TYPE
MNEMONIC ::= Instruction name in v-ext specification. Replace '.' with '_'.
RET_TYPE ::= SEW LMUL
SEW ::= ( i8 | i16 | i32 | i64 | u8 | u16 | u32 | u64 | f16 | f32 | f64 )
LMUL ::= ( m1 | m2 | m4 | m8 | mf2 | mf4 | mf8 )
Example:
vadd.vv vd, vs2, vs1:
vint8m1_t vadd_vv_i8m1(vint8m1_t vs2, vint8m1_t vs1, size_t vl);
vwaddu.vv vd, vs2, vs1:
vint16m2_t vwaddu_vv_i16m2(vint8m1_t vs2, vint8m1_t vs1, size_t vl);
If intrinsics have the same return type under different input types, we could not use general naming rules directly on these intrinsics. It will cause the same intrinsic names for different input types.
This section lists all exceptional cases for intrinsic naming.
It does not encode return type into vector store. There is no return data for store operations. Instead, use the type of store data to name the intrinsics.
Example:
vse8.v vs3, (rs1):
void vse8_v_i8m1(int8_t *rs1, vint8m1_t vs3, size_t vl);
The result of vmadc
and vmsbc
is mask types. Because we use the ratio SEW
/LMUL
to name the mask types and multiple (SEW
, LMUL
) pairs map to the same ratio, in addition to use the return type to name the intrinsics, we also encode the input types to distinguish these intrinsics.
Example:
vmadc.vv vd, vs2, vs1:
vbool8_t vmadc_vv_i8m1_b8(vint8m1_t vs2, vint8m1_t vs1, size_t vl);
The result of comparison instructions is mask types. Becuase we use the ratio SEW
/LMUL
to name the mask types and multiple (SEW
, LMUL
) pairs map to the same ratio, in addition to use the return type to name the intrinsics, we also encode the input types to distinguish these intrinsics.
Example:
vmseq.vv vd, vs2, vs1:
vbool8_t vmseq_vv_i8m1_b8(vint8m1_t vs2, vint8m1_t vs1, size_t vl);
vbool8_t vmseq_vv_i16m2_b8(vint16m2_t vs2, vint16m2_t vs1, size_t vl);
The scalar input and output operands are held in element 0 of a single vector register. Use LMUL = 1 in the return type. To distinguish different intrinsics with different input types, encode the input type and the result type in the name.
Example:
vredsum.vs vd, vs2, vs1:
vint8m1_t vredsum_vs_i8m1_i8m1(vint8m1_t dest, vint8m1_t vs2, vint8m1_t vs1, size_t vl);
vint8m1_t vredsum_vs_i8m2_i8m1(vint8m1_t dest, vint8m2_t vs2, vint8m1_t vs1, size_t vl);
vint8m1_t vredsum_vs_i8m4_i8m1(vint8m1_t dest, vint8m4_t vs2, vint8m1_t vs1, size_t vl);
vint8m1_t vredsum_vs_i8m8_i8m1(vint8m1_t dest, vint8m8_t vs2, vint8m1_t vs1, size_t vl);
The return type of vcpop.m
and vfirst.m
is apparently an integer. Do not encode the return type into it. Instead, encode the input type to it.
Example:
vcpop.m rd, vs2:
unsigned long vcpop_m_b1(vbool1_t vs2, size_t vl);
unsigned long vcpop_m_b2(vbool2_t vs2, size_t vl);
To move the element 0 of a vector to a scalar, encode the input vector type and the output scalar type.
Example:
vmv.x.s rd, vs2:
int8_t vmv_x_s_i8m1_i8 (vint8m1_t vs2, size_t vl);
int8_t vmv_x_s_i8m2_i8 (vint8m2_t vs2, size_t vl);
int8_t vmv_x_s_i8m4_i8 (vint8m4_t vs2, size_t vl);
int8_t vmv_x_s_i8m8_i8 (vint8m8_t vs2, size_t vl);
Some V-extension instructions have both vector and scalar operands, and the scalar operands may be truncated or extended depending on the relative widths of XLEN (or FLEN) and SEW. The intrinsics API hides this detail from the C programmer by using fixed-width integer (or floating-point) types, as follows:
Example:
// Use uint8_t for op2.
vuint8m1_t vadd_vx_u8m1(vuint8m1_t op1, uint8_t op2, size_t vl);
// Use uint64_t for op2.
vuint64m1_t vadd_vx_u64m1(vuint64m1_t op1, uint64_t op2, size_t vl);
To support the implied semantics, the compiler may generate multiple instructions for the intrinsics.
(More generally, this API does not constrain the compiler in which instructions it actually generates.)
For example, to support vmv_x_s_i64m1_i64
when XLEN = 32, the compiler may generate vmv.x.s
, vslide1down.vx
, and another vmv.x.s
, all with SEW = 32, to extract the full 64 bits into two X-registers.
RISC-V "V" extension only has "merge in output" semantic. Intrinsics with mask has two additional arguments, mask
and maskedoff
.
vd = vop(mask, maskedoff, arg1, arg2)
vd[i] = maskedoff[i], if mask[i] == 0
vd[i] = vop(arg1[i], arg2[2]), if mask[i] == 1
In general, the naming rule of intrinsic with mask v0.t is
INTRINSIC_WITH_MASK ::= INTRINSIC '_m'
Example:
vadd.vv vd, vs2, vs1, v0.t:
vint8m1_t vadd_vv_i8m1_m(vbool8_t mask, vint8m1_t maskedoff, vint8m1_t vs2, vint8m1_t vs1, size_t vl);
If the intrinsics are always masked, there is no need to append _m
to the intrinsic. For example, the vmerge
instructions are always masked.
Example:
vmerge.vvm vd, vs2, vs1, v0:
vint8m1_t vmerge_vvm_i8m1(vbool8_t mask, vint8m1_t vs2, vint8m1_t vs1, size_t vl);
vcompress.vm vd, vs2, vs1:
vint8m1_t vcompress_vm_i8m1(vbool8_t vs1, vint8m1_t maskedoff, vint8m1_t vs2, size_t vl);
There are two additional masking semantics: zero in output semantic and don't care in output semantic. Users could leverage merge in output intrinsics to simulate these two additional masking semantics.
The intrinsics with ending _m
have no argument to specify tail policy. Almost all _m
intrinsics are tail agnostic and mask undisturbed. See Policy Intrinsic Functions for the more detail.
Example:
// Don't care in output semantic
vint8m1_t vadd_vv_i8m1_m(vbool8_t mask, vundefined_i8m1(), vint8m1_t vs2, vint8m1_t vs1, size_t vl);
There is no maskedoff
argument for store operations. The value of maskedoff
already exists in memory.
Example:
vse8.v vs3, (rs1), v0.t:
void vse8_v_i8m1_m(vbool8_t mask, int8_t *rs1, vint8m1_t vs3, size_t vl);
The result of reductions is put in element 0 of the output vector. There is no maskedoff
argument for reduction operations.
Example:
vredsum.vs vd, vs2, vs1, v0.t:
vint8m1_t vredsum_vs_i8m2_i8m1_m(vbool4_t mask, vint8m1_t dest, vint8m2_t vs2, vint8m1_t vs1, size_t vl);
The result of merge operations comes from their two source operands. Merge intrinsics have no maskedoff
argument.
Example:
vmerge.vvm vd, vs2, vs1, v0:
vint8m1_t vmerge_vvm_i8m1_m(vbool8_t mask, vint8m1_t vs2, vint8m1_t vs1, size_t vl);
The intrinsics encode the policy (or policies) explicitly in function name's suffix if operations support those policies. Users could use policy intrinsic functions to control tail and inactive elements. Please refer to 3.4.3 Vector Tail Agnostic and Vector Mask Agnostic and 5.4 Prestart, Active, Inactive, Body, and Tail Element Definitions for fundamental definitions.
The general naming rule is as the following. The interface differs between instructions and will be enumerated in the following of this section.
INTRINSIC_WITH_TU ::= INTRINSIC '_tu'
INTRINSIC_WITH_TA ::= INTRINSIC '_ta'
INTRINSIC_WITH_MASK_MU ::= INTRINSIC '_mu'
INTRINSIC_WITH_MASK_MA ::= INTRINSIC '_ma'
INTRINSIC_WITH_MASK_TUM ::= INTRINSIC '_tum'
INTRINSIC_WITH_MASK_TAM ::= INTRINSIC '_tam'
INTRINSIC_WITH_MASK_TU_MA ::= INTRINSIC '_tuma'
INTRINSIC_WITH_MASK_TA_MA ::= INTRINSIC '_tama'
INTRINSIC_WITH_MASK_TU_MU ::= INTRINSIC '_tumu'
INTRINSIC_WITH_MASK_TA_MU ::= INTRINSIC '_tamu'
Masked? | TU? | MU? | Intrinsic |
---|---|---|---|
No | No | N/A | vadd_vv_<ty>_ta(op1, op2, vl) and vadd_vv_<ty>(op1, op2, vl) |
No | Yes | N/A | vadd_vv_<ty>_tu(merge,op1, op2, vl) |
Yes | No | No | vadd_vv_<ty>_tama(mask, op1, op2, vl) |
Yes | No | Yes | vadd_vv_<ty>_tamu(mask, merge, op1, op2, vl) , and vadd_vv_<ty>_m(mask, merge, op1, op2, vl) |
Yes | Yes | No | vadd_vv_<ty>_tuma(mask, merge, op1, op2, vl) |
Yes | Yes | Yes | vadd_vv_<ty>_tumu(mask, merge, op1, op2, vl) |
Masked? | TU? | MU? | Intrinsic |
---|---|---|---|
No | No | N/A | vmv_v_x_<ty>_ta(src, vl) and vmv_v_x_<ty>(src, vl) |
No | Yes | N/A | vmv_v_x_<ty>_tu(merge, src, vl) |
Masked? | TU? | MU? | Intrinsic |
---|---|---|---|
No | No | N/A | vmacc_vv_<ty>_ta(vd, vs1, vs2, vl) |
No | Yes | N/A | vmacc_vv_<ty>_tu(vd, vs1, vs2, vl) and vmacc_vv_<ty>(vd, vs1, vs2, vl) |
Yes | No | No | vmacc_vv_<ty>_tama(mask, vd, vs1, vs2, vl) |
Yes | No | Yes | vmacc_vv_<ty>_tamu(mask, vd, vs1, vs2, vl) and vmacc_vv_<ty>_m(mask, vd, vs1, vs2, vl) |
Yes | Yes | No | vmacc_vv_<ty>_tuma(mask, vd, vs1, vs2, vl) |
Yes | Yes | Yes | vmacc_vv_<ty>_tumu(mask, vd, vs1, vs2, vl) |
Masked? | TU? | MU? | Intrinsic |
---|---|---|---|
No | No | N/A | vredsum_vs_<ty>_ta(vector, scalar, vl) |
No | Yes | N/A | vredsum_vs_<ty>_tu(merge, vector, scalar, vl) and vredsum_vs_<ty>(merge, vector, scalar, vl) |
Yes | No | N/A | vredsum_vs_<ty>_tam(mask, vector, scalar, vl) |
Yes | Yes | N/A | vredsum_vs_<ty>_tum(mask, merge, vector, scalar, vl) and vredsum_vs_<ty>_m(mask, merge, vector, scalar, vl) |
Intrinsics for the instructions which only have mask policy. (ex. vector comparison, vmsbf.m
, vmsif.m
and vmsof.m
.)
Masked? | TU? | MU? | Intrinsic |
---|---|---|---|
No | N/A | N/A | vmseq_vv_i8m1_b8(op, op2, vl) |
Yes | N/A | No | vmseq_vv_i8m1_b8_ma(mask, op1, op2, vl) |
Yes | N/A | Yes | vmseq_vv_i8m1_b8_mu(mask, merge, op1, op2, vl) and vmseq_vv_i8m1_b8_m(mask, merge, op1, op2, vl) |
NOTE: N/A means it is a compiler-defined value. For example, compiler could choose the same policy with adjacent instruction to save the vsetvli instruction.
There are some special cases for policy functions listed as below:
- The following instructions doesn't have an masked intrinsic, therefore it only supports
_ta
and_tu
:vadc
,vsbc
vmv.s.x
,vfmv.s.f
- Vector integer and floating-point move instructions
- Vector integer and floating-point merge instructions
- Vector compress instructions
- The following instructions may only use mask value, it only supports
_ma
and_mu
:vmsbf.m
,vmsif.m
,vmsof.m
- Vector integer and floating-point comparison instructions
- The following instructions do not have policy intrinsic functions:
vfirst.m
,vmv.x.s
,vfmv.f.s
,vcpop.m
vmadc
,vmsbc
,vlm.v
- Vector store instructions
vslideup
andvslidedown
still have dest operand in_ta
and_tama
policy.
The policy is encoded as suffix for overloading functions just like non-overloading ones.
vadd.vv vd, vs2, vs1:
// unmasked
vint32m1_t vadd(vint32m1_t op1, vint32m1_t op2, size_t vl); // alias to vadd_vv_i32m1
vint32m1_t vadd_ta(vint32m1_t op1, vint32m1_t op2, size_t vl);
vint32m1_t vadd_tu(vint32m1_t merge, vint32m1_t op1, vint32m1_t op2, size_t vl);
// masked
vint32m1_t vadd(vbool32_t mask, vint32m1_t merge, vint32m1_t op1, vint32m1_t op2, size_t vl); // alias to vadd_vv_i32m1_m
vint32m1_t vadd_tama(vbool32_t mask, vint32m1_t op1, vint32m1_t op2, size_t vl);
vint32m1_t vadd_tamu(vbool32_t mask, vint32m1_t merge, vint32m1_t op1, vint32m1_t op2, size_t vl);
vint32m1_t vadd_tuma(vbool32_t mask, vint32m1_t merge, vint32m1_t op1, vint32m1_t op2, size_t vl);
vint32m1_t vadd_tumu(vbool32_t mask, vint32m1_t merge, vint32m1_t op1, vint32m1_t op2, size_t vl);
The overloading intrinsics for vmv.s.x
, vfmv.s.f
, vid.v
and viota.m
are special cases from other overloading intrinsics.
Their arguments for _ta
and _tama
make them impossible to overload without specifying the return type, making them identical with the non-overloading ones.
vid.v vd, vm
// unmasked
vuint32m1_t vid_tu(vuint32m1_t merge, size_t vl);
vuint32m1_t vid_v_u32m1_ta(size_t vl);
// masked
vuint32m1_t vid_tuma(vbool32_t mask, vuint32m1_t merge, size_t vl);
vuint32m1_t vid_tumu(vbool32_t mask, vuint32m1_t merge, size_t vl);
vuint32m1_t vid_tamu(vbool32_t mask, vuint32m1_t merge, size_t vl);
vuint32m1_t vid_v_u32m1_tama(vbool32_t mask, size_t vl);
vmv.s.x
and reduction operations will only modify the first element of the destination vector. Users could keep the original values of the remaining elements in the destination vector through dest
argument in these intrinsics.
Vector slideup works by leaving elements 0<i<OFFSET
undisturbed, so it need the destination operand as input for correctness. Vector slidedown also have destination operand, but it's used for interface consistent with vslideup, not for correctness.
Example:
vint8m1_t vmv_s_x_i8m1(vint8m1_t dest, int8_t src, size_t vl);
vint8m1_t vredsum_vs_i8m1_i8m1(vint8m1_t dest, vint8m1_t vs2, vint8m1_t vs1, size_t vl);
vint8m1_t vredsum_vs_i8m2_i8m1_m(vbool4_t mask, vint8m1_t dest, vint8m2_t vs2, vint8m1_t vs1, size_t vl);
vuint8m1_t vslide1up_vx_u8m1(vuint8m1_t dest, vuint8m1_t op1, uint8_1 op2, size_t vl);
SEW
and LMUL
are the static information for the intrinsics. The compiler will generate vsetvli when vtype is changed between operations.
Example:
vint8m1_t a, b, c, d;
vint16m2_t a2, b2, c2;
...
a2 = vwadd_vv_i16m2(a, b, vl);
b2 = vwadd_vv_i16m2(c, d, vl);
c2 = vadd_vv_i16m2(a2, b2, vl);
It will generate the following instructions.
vsetvli x0, vl, e8,m1
vwadd.vv a2, a, b
vwadd.vv b2, c, d
vsetvli x0, vl, e16,m2
vadd.vv c2, a2, b2
Be aware that when the ratio of LMUL/SEW
is changed, users need to ensure the vl
is correct for the following operations if using implicit vl intrinsics.
The semantic of C builtin operators, other than simple assignment, hasn't been decided yet. Simple assignment keeps the usual C semantics of storing the value on the right operand into the variable of the left operand.
This section lists all utility functions to help users program in V intrinsics easier.
These utility functions are used to initialize vector values. They could be used in masking intrinsics with don't care in output semantics.
Example:
vint8m1_t vundefined_i8m1()
Note: Any operation with vundefined_* ()
are undefined and unpredictable,
the only recommended usage is used as maskedoff operand; an additional note is
any operation with vundefined_* ()
will got unpredictable result, e.g.
vxor(vundefined (), vundefined ())
and vec a = vundefined(); vec b = vxor(a, a);
both are not guarantee result vector with zeros.
These utility functions help users to convert types between floating point and integer types. The reinterpreter intrinsics only change the types of underlying contents. It is a nop operation.
Example:
// Convert floating point to signed integer types.
vint64m1_t vreinterpret_v_f64m1_i64m1(vfloat64m1_t src)
// Convert floating point to unsigned integer types.
vuint64m1_t vreinterpret_v_f64m1_u64m1(vfloat64m1_t src);
These utility functions help users to convert types between signed and unsigned types. The reinterpreter intrinsics only change the types of underlying contents. It is a nop operation.
Example:
// Convert signed to unsigned types.
vuint8m1_t vreinterpret_v_i8m1_u8m1(vint8m1_t src)
These utility functions help users to convert types between SEW
s under the same LMUL
, e.g., convert vint32m1_t to vint64m1_t. The reinterpreter intrinsics only change the types of underlying contents. It is a nop operation. It will generate vsetvli
by the following vector operation for the new type.
Example:
// Convert SEW under the same LMUL.
vint64m1_t vreinterpret_v_i32m1_i64m1(vint32m1_t src)
These utility functions help users to truncate or extent current LMUL under same SEW regardless of vl (it won't change content of vl register)
The LMUL extension result of extension part are undefined value.
Example:
// LMUL Truncation, vlmul_trunc_v_<src_lmul>_<target_lmul>
vint64m1_t vlmul_trunc_v_i64m2_i64m1 (vint64m2_t op1);
vint64m1_t vlmul_trunc_v_i64m4_i64m1 (vint64m4_t op1);
vint64m2_t vlmul_trunc_v_i64m4_i64m2 (vint64m4_t op1);
// LMUL Extension, vlmul_ext_v_<src_lmul>_<target_lmul>
vint64m2_t vlmul_ext_v_i64m1_i64m2 (vint64m1_t op1);
vint64m4_t vlmul_ext_v_i64m1_i64m4 (vint64m1_t op1);
vint64m8_t vlmul_ext_v_i64m1_i64m8 (vint64m1_t op1);
These utility functions help users to insert or extract smaller LMUL under same SEW.
Example:
// Insert an smaller LMUL, vset_v_<src_lmul>_<target_lmul>
vint32m2_t vset_v_i32m1_i32m2 (vint32m2_t dest, size_t index, vint32m1_t val);
vint32m4_t vset_v_i32m1_i32m4 (vint32m4_t dest, size_t index, vint32m1_t val);
vint32m4_t vset_v_i32m2_i32m4 (vint32m4_t dest, size_t index, vint32m2_t val);
// Extract an smaller LMUL, vget_v_<src_lmul>_<target_lmul>
vint32m1_t vget_v_i32m2_i32m1 (vint32m2_t src, size_t index);
vint32m1_t vget_v_i32m4_i32m1 (vint32m4_t src, size_t index);
vint32m2_t vget_v_i32m4_i32m2 (vint32m4_t src, size_t index);
Overloaded Interface have shorter function name and support less number of intrinsic functions.
Overloaded interface are always keep full function name with removing the suffix.
Compiler could support overloaded interface optionally. Preprocessor macro __riscv_v_intrinsic_overloading
is defined when overloaded interface is available.
Example:
vadd.vv, vadd.vx, vadd.vi will have an unified interface vadd() for them.
vint8m1_t vadd(vint8m1_t op1, vint8m1_t op2, size_t vl);
// The compiler will choose the following intrinsic
vint8m1_t vadd_vv_i8m1(vint8m1_t op1, vint8m1_t op2, size_t vl);
vint8m2_t vadd(vint8m2_t op1, vint8m2_t op2, size_t vl);
// The compiler will choose the following intrinsic
vint8m2_t vadd_vv_i8m2(vint8m2_t op1, vint8m2_t op2, size_t vl);
vint8m1_t vadd(vint8m1_t op1, int8_t op2, size_t vl);
// The compiler will choose the following intrinsic
vint8m1_t vadd_vx_i8m1(vint8m1_t op1, int8_t op2, size_t vl);
vint8mf8_t vadd(vbool64_t mask, vint8mf8_t maskedoff, vint8mf8_t op1, vint8mf8_t op2, size_t vl);
// The compiler will choose the following intrinsic
vint8mf8_t vadd_vv_i8mf8_m (vbool64_t mask, vint8mf8_t maskedoff, vint8mf8_t op1, vint8mf8_t op2, size_t vl);
The unsupported overloading functions are based on the types of input/return arguments:
- Input arguments are scalar type alone. (non-masked vle/vlse, etc.)
- Input argument is empty. (vmclr.m/vmset.m/vid.v)
- Input boolean vector argument with return type of a non boolean vector. (viota)
Append one more suffix from mnemonic in the function names to distinct the different operation.
Example:
// vint16m1_t vfcvt_x_f_v_i16m1 (vfloat16m1_t src, size_t vl);
vint16m1_t vfcvt_x (vfloat16m1_t src, size_t vl);
Append output type in the function names to avoid different operations have the same type of argument.
Example:
// vuint8mf4_t vreinterpret_v_u16mf4_u8mf4(vuint16mf4_t src)
vuint8mf4_t vreinterpret_u8mf4(vuint16mf4_t src)
// vfloat16mf4_t vreinterpret_v_u16mf4_f16mf4(vuint16mf4_t src)
vfloat16mf4_t vreinterpret_f16mf4(vuint16mf4_t src)
// vuint8mf8_t vlmul_trunc_v_u8m1_u8mf8(vuint8m1_t op1);
vuint8mf8_t vlmul_trunc_u8mf8(vuint8m1_t op1);
// vuint8mf4_t vlmul_trunc_v_u8m1_u8mf4(vuint8m1_t op1);
vuint8mf4_t vlmul_trunc_u8mf4(vuint8m1_t op1);
// vint8m2_t vget_v_i8m8_i8m2(vint8m8_t src, size_t index);
vint8m2_t vget_i8m2(vint8m8_t src, size_t index);
// vint8m4_t vget_v_i8m8_i8m4(vint8m8_t src, size_t index);
vint8m4_t vgeti8m4(vint8m8_t src, size_t index);
The scalar type promotions is not obvious if instruction supports source vector type is 2*SEW and SEW, so append
the vx/wx/vf/wf suffix in the function names.
(ex. vwadd[u].vx
/vwadd[u].wx
, vwsub[u].vx
/vwsub[u].wx
, vfwadd_vf
/vfwadd_wf
and vfwsub_vf
/vfwsub_wf
).
In order to consistent, vector-vector operations also append a suffix in the function name.
// Example: users need to specific explicit type for op2 if below functions have the same name.
vuint32mf2_t vwaddu_wx(vuint32mf2_t op1, uint16_t op2, size_t vl);
vuint64m1_t vwaddu_vx(vuint32mf2_t op1, uint32_t op2, size_t vl);
Original overloading name are confusing. Append one more suffix for readability. For example:
// Old.
vint8m1_t vmv (vint8m1_t src, size_t vl); // vmv.v.v
int8_t vmv (vint8m1_t src); // vmv.x.s
vint8m1_t vmv (vint8m1_t dst, int8_t src, size_t vl); // vmv.s.x
float16_t vfmv (vfloat16m1_t src); // vfmv.f.s
vfloat16m1_t vfmv (vfloat16m1_t dst, float16_t src, size_t vl); // vfmv.s.f
// New.
vint8m1_t vmv_v (vint8m1_t src, size_t vl); // vmv.v.v
int8_t vmv_x (vint8m1_t src); // vmv.x.s
vint8m1_t vmv_s (vint8m1_t dst, int8_t src, size_t vl); // vmv.s.x
float16_t vfmv_f (vfloat16m1_t src); // vfmv.f.s
vfloat16m1_t vfmv_s (vfloat16m1_t dst, float16_t src, size_t vl); // vfmv.s.f
Compiler should guarantee the correctness of vtype setting after vsetvl instruction. For example considering the widening multiply example as below.
vl = vsetvl_e16m4(n);
vfloat16m4_t vx = vle16_v_f16m4(ptr_x, vl);
// vsetvl_e32m8(vl); // No need to keep the same vl and change vtype manually
vfloat32m8_t vy = vle32_v_f32m8(ptr_y, vl);
// vsetvl_e16m4(vl); // No need to keep the same vl and change vtype manually
vfwmacc_vf_f32m8(vy, 2.0, vx, vl);
// vsetvl_e32m8(vl); // No need to keep the same vl and change vtype manually
vse32_v_f32m8(ptr_y, vy, vl);
This example has a vl
computed from vsetvl_e16m4
, and changing the type to vfloat32m8_t
in the middle.
With compiler's helping, users don't need to change vtype manually because vfloat16m4_t
and vfloat32m8_t
have the exact same number of elements (same SEW
/LMUL
ratio).
Noted that when using the different vtype intrinsic functions with a new SEW
/LMUL
ratio after vsetvl instruction, the result will raise an illegal-instruction exception.
The V extension spec mentions that the strided load/store instruction with stride of 0 could have different instruction to perform all memory accesses or fewer memory operations. Since needing all memory accesses isn't likely to be common, the compiler implementation is allowed to generate fewer memory operations with strided load/store intrinsics. In other words, compiler does not guarantee generating the all memory accesses instruction in strided load/store intrinsics with stride of 0. If the user needs all memory accesses to be performed, they should use an indexed load/store intrinsics with all zero indices.