Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] Support for C operators on RVV types #291

Open
jacquesguan opened this issue Oct 31, 2023 · 13 comments
Open

[Proposal] Support for C operators on RVV types #291

jacquesguan opened this issue Oct 31, 2023 · 13 comments
Labels
Revisit after v1.0 Features or problems we will revisit after the v1.0 release

Comments

@jacquesguan
Copy link

Now the only way to do calculation on RVV sizeless type is using the intrinsic. By example, if we want to add 2 whole scalable vector, we should use the following code:

vint16m1_t op1;
vint16m1_t op2;
vint16m1_t op3 = __riscv_vadd(op1, op2, -1);

My proposal is that we support using C operators (such as: arithmetic, bitwise and subscript) on RVV sizeless types. Then we could change to the following code:

vint16m1_t op1;
vint16m1_t op2;
vint16m1_t op3 = op1 + op2;

I think that using operators instead of builtin is more clear and would enable more ir/backend optimizations.

The C operators that should be supported includes the following classes:
arithmetic operators: +, -, *, /, %;
bitwise operators: &, |, ^;
compare operators: ==, !=, >, >=, <, <=;
shift operators: <<, >>;
subscript operators: [].

@jacquesguan
Copy link
Author

Now clang already supports some C operators for SVE sizeless types. I have a patch to support C operators for RVV sizeless types, https://reviews.llvm.org/D158259.

@chLFF
Copy link

chLFF commented Nov 1, 2023

But how to implement C operators with mask?

@nick-knight
Copy link
Collaborator

nick-knight commented Nov 1, 2023

But how to implement C operators with mask?

... And how to control mask policy?

Or how to use flexible VL? And how to control tail policy?

The proposed semantics are unmasked, with VL = VLMAX, which resolves these questions, but requires users who need those features to use intrinsics.

@dzaima
Copy link

dzaima commented Nov 1, 2023

If a ? b : c was supported (as gcc and clang do for fixed-length vectors, ...though only in C++), or some similar thing for a blend/merge, __riscv_vadd_vv_i32m1_mu(mask, vd, op1, op2, -1) could be mask ? op1+op2 : vd. (and fwiw, clang already can convert a __riscv_vmerge to masking a source operand)

For arithmetic operations (i.e. all things proposed here), tail-agnostic and mask-agnostic aren't really strictly necessary; the compiler could infer a smaller VL from uses/argument sources. e.g. these are valid transformations:

__riscv_vse32(ptr, __riscv_vadd(a, b, VLMAX), 2); // == proposed __riscv_vse32(ptr, a+b, 2);__riscv_vse32(ptr, __riscv_vadd(a, b, 2), 2);

vint32m1_t vec = __riscv_vadd(__riscv_vle32_v_i32m1(ptr, 2), b, VLMAX); // == proposed __riscv_vle32_v_i32m1(ptr, 2) + bvint32m1_t vec = __riscv_vadd(__riscv_vle32_v_i32m1(ptr, 2), b, 2);

and mask-agnostic afaik isn't really useful for arithmetic at all other than a hint (ignoring FP exceptions/vxsat, which should be rarely needed and isn't even supported by existing intrinsics yet anyway), and thus are replaceable with like mask ? op1+op2 : __riscv_vundefined_i32m1() if really desired for whatever reason.

There'll still be some cases where VL control is required (tail-undisturbed is a thing that's not easily replaced; I guess there's like __riscv_vmv_v(vd, a+b, vl)?), but it could still be worth having the C operators for when that's not the case.

@jacquesguan
Copy link
Author

The backend of compiler (not sure about gcc) has ability to transform mask ? op1+op2 : vd into masked instruction. So with implemention of the conditional expression, I think we could handle the masked cases.

For vl, I also agree with @dzaima. One of motivation of this proposal is simplifying the syntax of vector calculation when vl=vlmax. For the cases that still demand vl, I think we could keep using intrinsics.

@kito-cheng
Copy link
Collaborator

@jacquesguan thanks for raising this, this topic has raise long time ago, however it has stop for a while since we've discuss around explicitly VL or implicitly VL at that moment, but now we are settle down with the explicitly VL for a while so it's kind of obviously to define those operator as VLMAX semantics for now.

Anyway I want to express I am support this in general, and here is few comments around different things:

  • Unary operators like -, ! and ~. ++ and -- may just ignore.
  • How about vbool*_t? I guess we could support bitwise operators, also ! and/or ~.

If a ? b : c was supported (as gcc and clang do for fixed-length vectors, ...though only in C++),

I support this, it's customized type, so I think we are not necessary has same limitation, I mean we could support that for both C and C++.


Also I would like to add few more syntax sugar around tuple type:

  1. Brace initialization for tuple type (syntax sugar for vcreate)
vint32m1_t x, y;
vint32m1x2_t a = {x, y};
  1. subscript operators (syntax sugar for vget/vset)
vint32m1_t x, y;
vint32m1x2_t a;
a[1] = x;
y = a[1];

@artintel2017
Copy link

artintel2017 commented Nov 8, 2023

I've tried a similar approach using C++ template classes and operators, here is some example I achieved:

All that required is to enable rvv intrinsic types to be contained in regular C++ class/struct (which is not true for now, but I tried all the code below with modified riscv g++ from gcc 12) , then rest of the work is all done by C++ grammar features, no further compiler frontend support needed.

basic types

rvv_vector<uint32_t, m1> v0;
rvv_vector<uint64_t, m4> v1;
rvv_vector<uint16_t, mf2> v2;
rvv_vector<uint8_t, mf8> v3;
rvv_mask<8> vm1;
rvv_mask<64> vm2;

vlen setting:

size_t vl = rvv_vector<uint16_t, m2>::vsetvlmax(); // set max vl according to vtype and lmul
set_global_vl(13); // set global vl, all operators below will use it, until set vl again or specified vl in specific op
v3.store(data, vl); // certain functions can have specified vl
//most operators can only use global vl, since there is not enough slot

load store

uint16_t data[512] = {...}; //
rvv_vector<uint16_t, m2> v1, v2, v3;
v1.load(data);
vl = 15;
v1.load(data, vl);
v1.load_index_od(data, index, vl); // variations of load instrinsics
v1 = 12;
v3.store(data, vl);

arithmetic

rvv_vector<uint32_t, m1> vindex, v0, v1, v2, v3, v4, v5;
vindex.index(); // index operator, v[i] = i
v1 = 1; // v1[i] = C for all i = {0, 1, 2, ...}
v2 = vindex % 2; // v2[i] = vindex[i] % 2
v3 = vindex + v2; 
v3 += 3;
v4 = v2*v3;
v5 *= v4<<1; 
v6 = v2 & v5; // v6[i] = v2[i] & v5[i] for i = {0, 1, 2, 3, ...}
v6 = v1+v2-v3*v4; // conbinations

logical and mask operations, commonly used syntax in math libs like numpy

rvv_vector<uint32_t, m1> v0, v1, v2, v3;
vindex.index(); // vindex = {0,1,2,3, ... }
rvv_mask<8> vm1 = vindex==1; //  vm1[i] = 1 if i==1, else 0
auto vm2 = vindex%1!=0; // vm2[i] = 1 if i = 1,3,5,...
auto vm3 = vindex>5; // vm3[i]=1 if i>5
auto vm4 = v1&&v2 || vm3; // logic operations
v2 = v1[vm1]; // masked assignment
v2[vm1] = v1; // same effect as above
v2 = v1[vm4] + v0; // v2[i] = v1[i]+v2[i] if vm4[i]==1, else v2[i]
v3 = v1[vindex>1 && vindex<5 || vindex>8] * v2; // combinations

widen/narrow op:

rvv_vector<uint8_t, m1> v0, v1, v2;
auto v1 = v0.widen_op() + 1 // rvv_vector<uint16_t, m2>
auto v2 = v1.narrow_op(); // rvv_vector<uint8_t, m1>
rvv_vector<uint64_t, m8> v4;
auto v5 = v4.narrow_op() + 1; // error, uint8 can't be narrowed
auto v6 = v5.widen_op() * 1; // error, m8 can't be widdend

@jan-wassenberg
Copy link

Just a quick +1 from Highway, we'd love to have operators. Currently we're requiring user code to write Div() or MaskedDivOr(), it would be nice to avoid that.

I agree with dzaima, _mu is the only thing we'd use (why use masks otherwise?). And VLMAX is also fine, one can also use masks for tail/remainder handling.

@jacquesguan
Copy link
Author

jacquesguan commented Nov 10, 2023

With @kito-cheng 's comment, let me conclude the operators we would support for RVV:

For RVV sizeless vector type:
unary operators:
-, +
!, ~
++, --

binary operators:
arithmetic operators: +, -, *, /, %;
bitwise operators: &, |, ^;
compare operators: ==, !=, >, >=, <, <=;
shift operators: <<, >>;

subscript operators: [].

ternary operatros : ?

For RVV sizeless mask vector type:
Only support:
!, ~, &, |, ^;

For RVV sizeless vector tuple type:
brace initialization {}
subscript operators []

@eopXD
Copy link
Collaborator

eopXD commented Nov 11, 2023

I see subscript operators for non-tuple types too, do you intend to propose declaration for an array of RVV non-tuple type?

@jacquesguan
Copy link
Author

I see subscript operators for non-tuple types too, do you intend to propose declaration for an array of RVV non-tuple type?

Similar to fixed length vectors, subscript operator for sizeless vectors means getting the i th element from the vector. By example, v[i] will lower to extractelement <vscale x n x ty> v, i64 i in LLVM.

@sh1boot
Copy link

sh1boot commented Jan 19, 2024

As I mentioned in #13, if you use the architecture-independent vector extension in Clang then you can use C operators for the bulk of your code and dip into intrinsics when you need to do something esoteric.

I'll just repeat that here, because I don't want existing functionality to be overlooked and duplicated.

Trouble is, that extension forces you to specify the size of the vector at compile time, and it picks RVV types which it thinks can do the job; setting VL appropriately for portable code. That's where I think an extension would be needed -- the ability to make that vector type sizeless.

And also it doesn't work in GCC.

@rofirrim rofirrim added the Revisit after v1.0 Features or problems we will revisit after the v1.0 release label Nov 18, 2024
@rofirrim
Copy link
Collaborator

I suggest we revisit this after 1.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Revisit after v1.0 Features or problems we will revisit after the v1.0 release
Projects
None yet
Development

No branches or pull requests

10 participants