Comments (14)
All the vector values have the bitwidth encoded in them, why not the scalar? That's what 'stdint.h' is for. Using 'long' for anything is non-portable (illustrated by the fact RV64's long is 64 bits vs. 32 bits in several other architectures; Intel's had the same problem in some old MKL interface where x86-64 & IA64 where behaving differently...)., I'd avoid it if possible, and use the types from stdint.h everywhere:
vuint64m1_t vslide1up_vs_u64m1 (vuint64m1_t src, uint64_t value);
vint64m1_t vslide1up_vs_u64m1 (vint64m1_t src, int64_t value);
vuint32m1_t vslide1up_vs_i32m1 (vuint32m1_t src, uint32_t value);
vint32m1_t vslide1up_vs_i32m1 (vint32m1_t src, int32_t value);
The semantic is well-defined to the user (including sign-extension or not), then the compiler only needs to make things work as expected (... which brings us back to our discussion of semantic vs. 1-1 mapping in a way...).
Cordially,
from rvv-intrinsic-doc.
vslide1up's rs1 is XLEN
size register,
vslide1up.vx vd, vs2, rs1, vm # vd[0]=x[rs1], vd[i+1] = vs2[i]
so in RV64, interface would looks like
vint32m1_t vslide1up_vs_i32m1(vint32m1_t src, int64_t value);
vint64m1_t vslide1up_vs_i64m1(vint64m1_t src, int64_t value);
in RV32,
vint32m1_t vslide1up_vs_i32m1(vint32m1_t src, int32_t value);
vint64m1_t vslide1up_vs_i64m1(vint64m1_t src, int32_t value);
Explicit type is clear but unfortunately RV32 and RV64 can not share the same api interface.
It's why we choose long type for value.
from rvv-intrinsic-doc.
@zakk0610 You don't have to have both interfaces if the hardware behave differently, but it helps.
Let me try to explain what I mean. It would be possible for the headers to look like:
#if __riscv_xlen == 64
vint32m1_t vslide1up_vs_i32m1(vint32m1_t src, int64_t value);
vint64m1_t vslide1up_vs_i64m1(vint64m1_t src, int64_t value);
#elif __riscv_xlen == 32
vint32m1_t vslide1up_vs_i32m1(vint32m1_t src, int32_t value);
vint64m1_t vslide1up_vs_i64m1(vint64m1_t src, int32_t value);
#else
#error "Oups"
#endif
This means code using vslide1up will have to be aware of discrepancy. It's a nuisance, but it reflects the hardware... a bit too much for my (software) taste.
I'd much rather have an 'homogeneous' interface, where the code will not compile on the wrong bit-width. I think the names should be considered globally rather than per-XLEN, which makes those part of the conflicting namespace from #3. So I first at would suggest something along the line of (I probably don't use the proper naming to discriminate, but you'll get the point):
#if __riscv_xlen == 64
vint32m1_t vslide1up_vs64_i32m1(vint32m1_t src, int64_t value);
vint64m1_t vslide1up_vs64_i64m1(vint64m1_t src, int64_t value);
#elif __riscv_xlen == 32
vint32m1_t vslide1up_vs32_i32m1(vint32m1_t src, int32_t value);
vint64m1_t vslide1up_vs32_i64m1(vint64m1_t src, int32_t value);
#else
#error "Oups"
#endif
The code won't compile on the wrong architecture, so software is reasonably safe.
Ultimately, it should be possible to implement the 'semantic variants' of those, where for example vslide1up_vs32_i32m1(vint32m1_t src, int32_t value);
will have the same behavior than vslide1up_vs64_i32m1
in RV64GCV, but doesn't need to ignore the upper 32 bits of the scalar - trivial to implement.
Similarly, vslide1up_vs64_i64m1
in RV32GCV would have to use two vector instructions, moving 32 bits at a time from the pair of scalar register (used to implement the scalar type int64_t) into the (semantically) lower 64 bits of the vint64m1_t vector - less trivial, but not excessively complex.
This way, the developer doesn't have to worry about implementation details, it's taken care of by the tools - or it doesn't compile if it won't work as expected. It may hide a bit the performance question, but that's a different problem.
from rvv-intrinsic-doc.
@rdolbeau
Sorry, I don't understand what long is non-portable, long type is is corresponding toXLEN
so it's workable on RV32 and RV64 platform.
In the future, if we have RV128GCV, I think the interfaces would become more complicated in your approach.
I would prefer below declaration or detect an error in compiler time:
#if __riscv_xlen == 64
vint32m1_t vslide1up_vs_i32m1(vint32m1_t src, long value);
vint32m1_t vslide1up_vs_u32m1(vunt32m1_t src, unsigned long value);
vint64m1_t vslide1up_vs_i64m1(vint64m1_t src, long value);
vint64m1_t vslide1up_vs_i64m1(vint64m1_t src, unsigned long value);
#elif __riscv_xlen == 32
vint32m1_t vslide1up_vs_i32m1(vint32m1_t src, long value);
vint32m1_t vslide1up_vs_u32m1(vunt32m1_t src, unsigned long value);
#else
#error "Oups"
#endif
because in RV32, SEW=64, native HW does not support unsigned extension to SEW.
ex. vint64m1_t vslide1up_vs_u64m1(vunt64m1_t src, unsigned long value); is illegal interface
ps. I expect any one to many instruction expansion should happen in higher abstraction layer or semantic intrinsics layer.
from rvv-intrinsic-doc.
@zakk0610 'long' is not portable as the amount of data it represents is variable depending on the machine on which you're working. You're right that it's always XLEN on RISC-V, but XLEN varies. So it can hold 32 (RV32, x86) or 64 bits (RV64, IA64).
Algorithms seldom work that way. They tend to work with a know amount of data (usually for integer) or a required accuracy (usually for FP). That's illustrated by the vector types: you don't use 'vint or vlong', you use 'vint32' or 'vint64'.
For me, anything that inject/extract data in/from a vector should follow the same principle, and specify the amount of bits in the source code - thus using 'int32_t' or 'int64_t', rather than 'int' or 'long' - because the later will vary between platforms despite the fact that the SEW doesn't. SO you find yourself in the situation above, where you need to have 'long' means either 32 bits or 64 bits and add a level of complexity.
With my proposal, ultimately (i.e. once 'semantic' is implemented) you don't need to test for architecture in the header:
vint32m1_t vslide1up_vs64_i32m1(vint32m1_t src, int64_t value);
vint64m1_t vslide1up_vs64_i64m1(vint64m1_t src, int64_t value);
vint32m1_t vslide1up_vs32_i32m1(vint32m1_t src, int32_t value);
vint64m1_t vslide1up_vs32_i64m1(vint64m1_t src, int32_t value);
(... you can have the unsigned variant as well ...)
This is fully specified on either RV32 or RV64. The code generated will be different, sure, but that's the compiler problem, not the problem or whomever is implementing the algorithm.
I do agree to an extant with you post-scriptum ; you could have the 'native' version (perhaps even using the native 'long'), and then have the 'semantic' version that would be defined as above, using bit-fixed width for scalar, illustrating this is less 'native' and more 'semantic'.
from rvv-intrinsic-doc.
@zakk0610 One more thing; when you say 'ex. vint64m1_t vslide1up_vs_u64m1(vunt64m1_t src, unsigned long value); is illegal interface' - I disagree. What I think you should say is that the operation is not supported as a single instruction in hardware. But the semantic of the function is well defined (if you know how big a 'long' is...). It's perfectly implementable by moving 32 bits twice, the first all-0, the second the value parameter.
That's what I started our discussions by insisting on the difference between '1-to-1 maping' intrinsics and 'semantic' intrinsics. The first category is a pain to use because it exposes a lot of the hardware subtlety that are not important to software developers. The second category is a bit more complex to support because some of the corner cases may need workarounds in the compiler to handle those subtleties, but it allows for much easier writing/debugging and much cleaner code.
Edit: it's also why you often need to reinterpret data - to be able to implement the missing semantic, you sometimes need to fallback on other data width & some voodoo. The most egregious example I know of is that you don't have 8-bits shift in SSE/AVX (!), so you use 16-bit shifts + some masking... it's ugly, but it works! (and then you try to remember to fix that ugliness in other, cleaner SIMD ISA otherwise you get eggs on your face openzfs/zfs#9725 ;-) )
from rvv-intrinsic-doc.
Thanks for you patient, I understood what is drawback of interface design when putting long type in API.
I still think long type scalar is fine and only long type vector is terrible as you mentioned. I think it's why in current EPI and SiFive's proposal we only define long type for scalar value.
But you are right, we can make anything consistent and friendly in 'semantic' intrinsics layer, it is very helpful for user.
Edit: it's also why you often need to reinterpret data - to be able to implement the missing semantic, you sometimes need to fallback on other data width & some voodoo. The most egregious example I know of is that you don't have 8-bits shift in SSE/AVX (!), so you use 16-bit shifts + some masking... it's ugly, but it works! (and then you try to remember to fix that ugliness in other, cleaner SIMD ISA otherwise you get eggs on your face openzfs/zfs#9725 ;-) )
agree, reinterpret function is important :)
BTW, I am curious about does SSE/AVX have 'semantic' intrinsics to support 8-bits shift? or 'semantic' intrinsics is always implemented by intrinsic user?
from rvv-intrinsic-doc.
No, they don't have the 8-bits shift...
They do offer many 'semantic-like' intrinsics in the intrinsics guide (https://software.intel.com/sites/landingpage/IntrinsicsGuide), such as the whole SVML (Short Vector Math Library), but they're not really defined as such - it's more an 'ad-hoc' behavior of the compiler...
Most intrinsics will behave 'semantically' in practice. For instance, take "_mm_add_ps". It's documented as an SSE intrinsic, and it will generate an SSE ADDPS by default. But if you specify you want support only for AVX machines (e.g. "icc -xCORE-AVX-I"), then it will emit a 128-bits AVX VADDPS, VEX-encoded. If you're on a AVX-512 machine (with the VL extension, e.g. "icc -xCORE-AVX512"), you may even get a 128-bits VADDPS but EVEX-encoded instead, if it needs to get access to the extra 16 vector registers available in AVX-512. Of course, wider register will require the corresponding extension or better (_mm256_add_ps doesn't work unless you enable AVX, ...).
And beyond that, if you have something like _mm_add_ps(_mm_mul_ps), on an FMA-enabled machine, the compiler might combine them in a FMA operation if otherwise permitted to do so...
It's quite convenient in practice, but not well defined in theory... but what I do is practice so it works for me ;-)
from rvv-intrinsic-doc.
Got it, thanks :)
It looks like compiler can help to support semantically intrinsics .
In your example, _mm_add_ps
interface work on SSE and AVX because this intrinsic functions are implemented by GCC's vector operator, and AVX backend also supports vector operator.
It means if the intrinsic functions can be translated to compiler internal representation, intrinsics will behave 'semantically'.
In other case _mm_add_ss
intrinsic function would not work for AVX because it call SSE specific builtin function __builtin_ia32_addss
and it is only available when-msse
option is used. (https://gcc.gnu.org/onlinedocs/gcc/x86-Built-in-Functions.html)
from rvv-intrinsic-doc.
Why do we need to provide different intrinsics under different HW configuration? We could provide the unified interface for users. For example,
vint32m1_t vslide1up_vx_i32m1(vint32m1_t src, int32_t value);
vint64m1_t vslide1up_vx_i64m1(vint64m1_t src, int64_t value);
The type of scalar is consistent with the SEW
of vector types.
It should make sense for users. Although the code generation will be different under different HW configuration, it is the job of the compiler. The only thing users need to aware is that using int64_t to operate on vint64m1_t will be slower if XLEN = 32.
I think we have no need to provide intrinsics with long
type. I doubt the users will expect use long
to operate with vint8m1_t, vint16m1_t, etc.
When SEW <= XLEN, it still be one-to-one mapping. Only when SEW > XLEN, the compiler needs to generate a sequence of instructions to deal with it.
from rvv-intrinsic-doc.
@Hsiangkai Well, that's what I suggested in https://github.com/sifive/rvv-intrinsic-doc/issues/9#issuecomment-615171839 :-)
Then you run into the differences between intrinsics I tried to describe in https://github.com/sifive/rvv-intrinsic-doc/issues/7#issuecomment-615751884
from rvv-intrinsic-doc.
https://github.com/sifive/rvv-intrinsic-doc/commit/7d20e8477f3a4dbd11aac31293d4fd2a8795f33b
from rvv-intrinsic-doc.
I write a section for this issue.
https://github.com/sifive/rvv-intrinsic-doc/blob/master/rvv-intrinsic-rfc.md#scalar-in-vector-operations
from rvv-intrinsic-doc.
from rvv-intrinsic-doc.
Related Issues (20)
- [Requirement]: The RISC-V RVV vector intrinsic must include support for vector groups in the __riscv_vfredosum function HOT 4
- Type-relative overloads for vreinterpret, vlmul_ext, vlmul_trunc, etc. HOT 1
- How to use a class to wrap or derive from a sizeless vector type HOT 1
- Encode all the effects of vsetvl in the return type, for use in subsequent type deductions HOT 1
- Does `__riscv_v_intrinsic >= 1000000` imply overloaded intrinsics are supported?
- Create bibliography from reference section HOT 3
- Simple questions about inline assembly in vmv.x.s instruction HOT 2
- Asterisks are not subscripts
- the wrong result of "vmerge_vvm_i32m1" HOT 5
- ta,ma reduction destination with vl=0 HOT 1
- Clarify the consequences of vxsat not being handled by the intrinsics HOT 3
- Add a section with examples HOT 3
- Rename uses of {implicit,explicit}-frm into {Implicit, Explicit} FP rounding mode HOT 1
- Clarify the mapping of pseudo-intrinsics
- Clarify what float and double means HOT 1
- Fix authors in the document
- How to use LMUL in rvv-intrinsic? HOT 6
- Mismatched bfloat16 autogenerated files HOT 3
- Freeze the specification HOT 1
- `vfirst` and `vcpop` return types unexpectedly changed HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from rvv-intrinsic-doc.