Hi, Richard. Thanks for the comments.

>> If we use SELECT_VL to refer only to the target-independent ifn, I don't
>> see why this last bit is true.
Could you give me more details and information about this since I am not sure 
whether I catch up with you. 
You mean the current SELECT_VL is not an appropriate IFN?


>>Like I said in the previous message,
>>when it comes to determining the length of each control, the approach we
>>take for MIN_EXPR IVs should work for SELECT_VL IVs.  The point is that,
>>in both cases, any inactive lanes are always the last lanes.
>>E.g. suppose that, for one particular iteration, SELECT_VL decides that
>>6 lanes should be active in a loop with VF==8.  If there is a 2-control
>>rgroup with 4 lanes each, the first control must be 4 and the second
>>control must be 2, just as if a MIN_EXPR had decided that 6 lanes of
>>the final iteration are active.
>>What I don't understand is why this isn't also a problem with the
>>fallback MIN_EXPR approach.  That is, with the same example as above,
>>but using MIN_EXPR IVs, I would have expected:
>>  VF == 8
>>  1-control rgroup "A":
>>    A set by MIN_EXPR IV
>>  2-control rgroup "B1", "B2":
>>    B1 = MIN (A, 4)
>>    B2 = A - B1
>>and so the vectors controlled by A, B1 and B2 would all have different
>>lengths.
>>Is the point that, when using MIN_EXPR, this only happens in the
>>final iteration?  And that you use a tail/epilogue loop for that,
>>so that the main loop body operates on full vectors only?
In general, I think your description is correct and  comprehensive. 
I'd like to share more my understanding to make sure we are on the same page.

Take the example as you said:

FOR one  particular iteration, SELECT_VL decides that 6 lanes should be active 
in a loop with VF==8.
and 2-control rgroup with 4 lanes each
which means:

VF = 8;
each control VF = 4;

Total length = SELECT_VL(or MIN_EXPR) (remain, 8)
Then, IMHO, we can have 3 solutions to deduce the length of 2-control base on 
current flow we already built

Also, let me share "vsetvl" ISA spec:
ceil(AVL / 2) ≤ vl ≤ VF if  VF <AVL < (2 * VF)
"vl" is the number of the elements we need to process, "avl" = the actual 
number of elements we will process in the current iteration

Solution 1:

Total length = SELECT_VL (remain, 8) ===> suppose Total length value = 6

control 1 length = SELECT_VL (Total length, 4) ===> If we use "vsetvl" 
intruction to get the control 1 length,
 it can be 3 or 4, since RVV ISA: ceil(AVL / 2) ≤ vl ≤ VF if AVL < (2 * VF), 
the outcome of SELECT_VL may be Total length / 2 = 3
Depending on the hardware implementation of "vsetvli", Let's say some RVV CPU 
likes "even distribution" the outcome = 3

control 2 length = Total length - control 1 length  ===> 6 - 3 = 3 (if control 
1 = 3) or 6 - 4 = 2 (if control 1 = 4) .

Since RVV ISA gives the flexible definition of "vsetvli", we will end up with 
this deduction.

Solution 2:

Total length = SELECT_VL (remain, 8)  ====> 6
control 1 length = MIN_EXPR (Total length, 4)  ====> since use MIN, so always 4
control 2 length = Total length - control 1 length  ===> 6 - 4 = 2

Solution 3 (Current flow):

Total length = MIN_EXPR  (remain, 8)  ====> 6 only when the remain  = 6 in 
tail/epilogue, otherwise, it always be 8 in loop body.
control 1 length = MIN_EXPR (Total length, 4)  ====> since use MIN, so always 4
control 2 length = Total length - control 1 length  ===> Total length -  4

I'd like to say these 3 solutions all work for RVV. 
However, RVV length configuration unlike IBM or ARM SVE using a mask. (I would 
like to say mask or length they are the same thing, use for control of each 
operations).
For example, ARM SVE has 8 mask registers, whenever it generate a mask, it can 
be include in use list in the instructions, since ARM SVE use encoding to 
specify the mask
register.

For example:
If we are using solution 1 in a target that control by length and length is 
specified in general registers, we can simulate the codegen as below.

max length = select_vl (vf=8)
length 1 = select_vl (vf=4)
length 2 = max length - length 1
...
load (...use general register which storing length 1 let's said r0, r0 is 
specified in the load encoding)
...
load (...use general register which storing length 2 let's said r1, r1 is 
specified in the load encoding)
....

However, for RVV, we don't specify the length in the instructions encoding.
Instead, we have only one VL register, and every time we want to change the 
length, we need"vsetvli"

So for solution 1, we will have:

max length = vsetvli (vf=8)
length 1 = vsetlvi (vf=4)
length 2 = max length = length 1
...
vsetvli zero, length 1 <======insert by "VSETVL" PASS of RISC-V backend
load....
vsetvli zero, length 2 <======insert by "VSETVL" PASS of RISC-V backend
load....

"vsetlvi" instruction is the instruction much more expensive than the general 
scalar instruction (for example "min" is much cheaper than "vsetvli").
So I am 100% sure that solution 3 (current MIN flow in GCC) is much better than 
above:

max length = min (vf=8) ===> replaced "vsetli" by "min"
length 1 = min (vf=4) ===> replaced "vsetli" by "min"
length 2 = max length = length 1
...
vsetvli zero, length 1 <======insert by "VSETVL" PASS of RISC-V backend
load....
vsetvli zero, length 2 <======insert by "VSETVL" PASS of RISC-V backend
load....

This is much better than Solution 3 and avoid multiple switching of "VL" 
register by "vsetvli"

Ok, you may want ask if "min" is much cheaper than "vsetvli", why we need 
SELECT_VL?
The reason is I want to optimize the special case (single-rgoup), since rgroup 
is just using a single length, 
unlike multiple-rgroup control which has multiple length calculation statement:

Current flow of single-rgoup:

...
length = min (vf)
...
vsetvli zero. length <=== insert by VSETLVI PASS
load (pointer IV)
vadd.
...
pointer IV = pointer IV + VF

I want to optimize it into:

...
length = vsetvli (Vf)
... <=== not need to insert vsetvlli.
load (pointer IV)
vadd.
...
pointer IV = pointer IV + length (adjust in bytesize).

This flow is the same as RVV ISA and LLVM. 
And also base on "vsetvli" definition, we can allow "even distribution" in the 
last iterations.

Hope my description is clear, feel free to comment.
Thanks so much.



juzhe.zh...@rivai.ai
 
From: Richard Sandiford
Date: 2023-06-05 14:21
To: juzhe.zhong
CC: gcc-patches; rguenther
Subject: Re: [PATCH V2] VECT: Add SELECT_VL support
juzhe.zh...@rivai.ai writes:
> +  /* If we're using decrement IV approach in loop control, we can use output 
> of
> +     SELECT_VL to adjust IV of loop control and data reference when it 
> satisfies
> +     the following checks:
> +
> +     (a) SELECT_VL is supported by the target.
> +     (b) LOOP_VINFO is single-rgroup control.
> +     (c) non-SLP.
> +     (d) LOOP can not be unrolled.
> +
> +     Otherwise, we use MIN_EXPR approach.
> +
> +     1. We only apply SELECT_VL on single-rgroup since:
> +
> +     (1). Multiple-rgroup controls N vector loads/stores would need N pointer
> +   updates by variable amounts.
> +     (2). SELECT_VL allows flexible length (<=VF) in each iteration.
> +     (3). For decrement IV approach, we calculate the MAX length of the loop
> +   and then deduce the length of each control from this MAX length.
> +
> +     Base on (1), (2) and (3) situations, if we try to use SELECT_VL on
> +     multiple-rgroup control, we need to generate multiple SELECT_VL to
> +     carefully adjust length of each control.
 
If we use SELECT_VL to refer only to the target-independent ifn, I don't
see why this last bit is true.  Like I said in the previous message,
when it comes to determining the length of each control, the approach we
take for MIN_EXPR IVs should work for SELECT_VL IVs.  The point is that,
in both cases, any inactive lanes are always the last lanes.
 
E.g. suppose that, for one particular iteration, SELECT_VL decides that
6 lanes should be active in a loop with VF==8.  If there is a 2-control
rgroup with 4 lanes each, the first control must be 4 and the second
control must be 2, just as if a MIN_EXPR had decided that 6 lanes of
the final iteration are active.
 
I'm not saying the decision itself is wrong.  But I think the explanation
could be clearer.
 
> +     Such approach is very inefficient
> +     and unprofitable for targets that are using a standalone instruction
> +     to configure the length of each operation.
> +     E.g. RISC-V vector use 'vsetvl' to configure the length of each 
> operation.
 
What I don't understand is why this isn't also a problem with the
fallback MIN_EXPR approach.  That is, with the same example as above,
but using MIN_EXPR IVs, I would have expected:
 
  VF == 8
 
  1-control rgroup "A":
    A set by MIN_EXPR IV
 
  2-control rgroup "B1", "B2":
    B1 = MIN (A, 4)
    B2 = A - B1
 
and so the vectors controlled by A, B1 and B2 would all have different
lengths.
 
Is the point that, when using MIN_EXPR, this only happens in the
final iteration?  And that you use a tail/epilogue loop for that,
so that the main loop body operates on full vectors only?
 
Thanks,
Richard
 

Reply via email to