digitalmars.D - core.simd woes
- F i L (39/39) Aug 04 2012 core.simd vectors are limited in a couple of annoying ways.
- Denis Shelomovskij (6/7) Aug 05 2012 There is no boxing in D language itself. One should use library
- Manu (28/64) Aug 06 2012 I think core.simd is only designed for the lowest level of access to the
- jerro (24/27) Aug 06 2012 Since LDC and GDC implement intrinsics with an API different from
- Manu (28/51) Aug 07 2012 I can see your reasoning, but I think that should be in core.sse, or
- F i L (127/173) Aug 06 2012 Yes, I found, and have been referring to, your std.simd library
- F i L (2/4) Aug 06 2012 Excuse me, this should have said a factor of 4x, not 8x.
- Manu (79/198) Aug 07 2012 I'm not sure why the performance would suffer when placing it in a struc...
- F i L (63/171) Aug 07 2012 I've tried all combinations with align() before and inside the
- F i L (4/33) Aug 07 2012 Okay, disregard this. I see you where talking about your function
- Manu (1/39) Oct 02 2012
- David Nadlinger (3/6) Aug 08 2012 objdump, otool – depending on your OS.
- F i L (10/11) Aug 08 2012 Hey, nice tools. Good to know, thanks!
- F i L (62/62) Oct 01 2012 Not to resurrect the dead, I just wanted to share an article I
- Dmitry Olshansky (7/12) Oct 01 2012 Yeah, but it won't cover operators. If only opBinary could be defined at...
- Manu (15/74) Oct 02 2012 These are indeed common gotchas. But they don't necessarily apply to D, ...
- F i L (12/37) Oct 02 2012 Thanks for the insight (and the code examples, though I've been
- jerro (1/2) Oct 02 2012 What problems did you have with it? It seems to work fine for me.
- F i L (18/21) Oct 02 2012 Can you post an example of doing a simple arithmetic with two
- F i L (1/1) Oct 02 2012 Also, I'm using the LDC off the official Arch community repo.
- jerro (5/18) Oct 02 2012 Oh, that doesn't work for me either. I never tried to use those,
- F i L (29/32) Oct 02 2012 Yes the SIMD situation isn't entirely usable right now with DMD
- Iain Buclaw (10/30) Oct 03 2012 Then don't just talk about it, raise a bug - otherwise how do you
- jerro (3/6) Oct 03 2012 I'm trying to create a bugzilla account on that site now, but
- F i L (2/5) Oct 03 2012 I never received an email either. Is there a expected time delay?
- Manu (4/36) Oct 05 2012 I didn't realise vector literals like that were supported properly in th...
- Iain Buclaw (32/71) Oct 05 2012 They get passed to the backend as of 2.060 - so looks like the
- Manu (3/73) Oct 07 2012 Perfect!
- David Nadlinger (5/7) Oct 14 2012 Speaking of test – are they available somewhere? Now that LDC
- Iain Buclaw (6/14) Oct 14 2012 Could you pastebin a header generation of the gccbuiltins module? We
- Iain Buclaw (5/19) Oct 14 2012 http://dpaste.dzfl.pl/4edb9ecc
- jerro (3/7) Oct 14 2012 I have a fork of std.simd with LDC support at
- Manu (13/22) Oct 15 2012 thub.com/jerro/std.simd-tests>.
- Iain Buclaw (12/95) Oct 08 2012 I fixed them again.
- F i L (14/21) Oct 08 2012 Nice, not even DMD can do this yet. Can these changes be pushed
- Iain Buclaw (5/30) Oct 08 2012 I'm refusing to implement any intrinsic that is tied to a specific archi...
- F i L (13/15) Oct 08 2012 I see. So the __builtin_ia32_***() functions in gcc.builtins are
- Iain Buclaw (8/23) Oct 08 2012 gcc.builtins does something different depending on architecure, and
- Manu (10/25) Oct 09 2012 std.simd already does have a mammoth mess of static if(arch & compiler).
- Jacob Carlborg (4/15) Oct 09 2012 An alternative approach is to have one module per architecture or compil...
- jerro (6/8) Oct 09 2012 You mean like something like std.simd.x86_gdc? In this case a
- Simen Kjaeraas (10/17) Oct 09 2012 Nope, like:
- Jacob Carlborg (4/11) Oct 09 2012 Exactly, what he said.
- jerro (39/53) Oct 09 2012 I'm guessing the platform in this case would be the CPU
- Manu (9/61) Oct 10 2012 Perfect! You saved me writing anything at all ;)
- David Nadlinger (4/11) Oct 10 2012 pragma(always_inline) or something like that would be trivially
- Iain Buclaw (11/21) Oct 10 2012 y
- Manu (3/24) Oct 10 2012 Right, well that's encouraging then. Maybe all the pieces fit, and we ca...
- F i L (26/32) Oct 09 2012 Well, that's not really what I was suggesting. I was saying maybe
- F i L (4/36) Oct 09 2012 You know... now that I think about it, this is pretty much
- Manu (7/33) Oct 08 2012 core.simd just provides what the compiler provides in it's most primal
- Manu (3/42) Oct 08 2012 GCC offers perfectly good intrinsics anyway. And they're superior to the
- Iain Buclaw (6/51) Oct 08 2012 Provided that a) the architecture provides them, and b) you have the
- David Nadlinger (18/36) Oct 08 2012 No, the actual codegen is compilers-specific (and apparently
- Manu (10/20) Oct 09 2012 e
- David Nadlinger (6/10) Oct 14 2012 By the way, I just committed a patch to auto-generate GCC->LLVM
- F i L (2/6) Oct 14 2012 Your awesome, David!
- David Nadlinger (4/10) Oct 14 2012 Usually, yes, but in this case even I must admit that it was
- Iain Buclaw (11/48) Oct 09 2012 .)
- David Nadlinger (6/9) Oct 09 2012 That's obviously true, but not at all enough for most of the
- David Nadlinger (6/9) Oct 09 2012 That's obviously true, but not at all enough for most of the
- Walter Bright (6/11) Oct 14 2012 That is correct. I have little experience with SIMD on x86, and none on ...
- David Nadlinger (28/33) Oct 08 2012 The obligatory "me too" post:
- Manu (4/101) Oct 08 2012 Errr, that's not fixed...?
- Iain Buclaw (5/113) Oct 08 2012 I didn't say I compiled with optimisations - only -march=native. =)
- Manu (4/120) Oct 08 2012 Either way, that code is wrong. The prior code was correct (albeit with ...
- Manu (4/120) Oct 08 2012 Either way, that code is wrong. The prior code was correct (albeit with ...
- Manu (3/14) Oct 02 2012 I haven't considered/written an SSE2 fallback yet, but I expect some tri...
- Manu (53/87) Oct 02 2012 I actually haven't had time to try out the new 2.60 alignment changes in
core.simd vectors are limited in a couple of annoying ways. First, if I define: property pure nothrow { auto x(float4 v) { return v.ptr[0]; } auto y(float4 v) { return v.ptr[1]; } auto z(float4 v) { return v.ptr[2]; } auto w(float4 v) { return v.ptr[3]; } void x(ref float4 v, float val) { v.ptr[0] = val; } void y(ref float4 v, float val) { v.ptr[1] = val; } void z(ref float4 v, float val) { v.ptr[2] = val; } void w(ref float4 v, float val) { v.ptr[3] = val; } } Then use it like: float4 a, b; a.x = a.x + b.x; it's actually somehow faster than directly using: a.ptr[0] += b.ptr[0]; However, notice that I can't use '+=' in the first case, because 'x' isn't an lvalue. That's really annoying. Moreover, I can't assign a vector to anything other than a array of constant expressions. Which means I have to make functions just to assign vectors in a convenient way. float rand = ...; float4 vec = [rand, 1, 1, 1]; // ERROR: expected constant Now, none of this would be an issue at all if I could wrap core.simd vectors into custom structs... but doing that complete negates their performance gain (I'm guessing because of boxing?). It's a different between 2-10x speed improvements using float4 directly (depending on CPU), and only a few mil secs improvement when wrapping float4 in a struct. So, it's not my ideal situation, but I wouldn't mind at all having to use core.simd vector types directly, and moving things like dot/cross/normalize/etc to external functions, but if that's the case then I would _really_ like some basic usability features added to the vector types. features, and working with them is much nicer than using D's core.simd vectors.
Aug 04 2012
05.08.2012 7:33, F i L пишет:...I'm guessing because of boxing?...There is no boxing in D language itself. One should use library solutions for such functionality. -- Денис В. Шеломовский Denis V. Shelomovskij
Aug 05 2012
On 5 August 2012 06:33, F i L <witte2008 gmail.com> wrote:core.simd vectors are limited in a couple of annoying ways. First, if I define: property pure nothrow { auto x(float4 v) { return v.ptr[0]; } auto y(float4 v) { return v.ptr[1]; } auto z(float4 v) { return v.ptr[2]; } auto w(float4 v) { return v.ptr[3]; } void x(ref float4 v, float val) { v.ptr[0] = val; } void y(ref float4 v, float val) { v.ptr[1] = val; } void z(ref float4 v, float val) { v.ptr[2] = val; } void w(ref float4 v, float val) { v.ptr[3] = val; } } Then use it like: float4 a, b; a.x = a.x + b.x; it's actually somehow faster than directly using: a.ptr[0] += b.ptr[0]; However, notice that I can't use '+=' in the first case, because 'x' isn't an lvalue. That's really annoying. Moreover, I can't assign a vector to anything other than a array of constant expressions. Which means I have to make functions just to assign vectors in a convenient way. float rand = ...; float4 vec = [rand, 1, 1, 1]; // ERROR: expected constant Now, none of this would be an issue at all if I could wrap core.simd vectors into custom structs... but doing that complete negates their performance gain (I'm guessing because of boxing?). It's a different between 2-10x speed improvements using float4 directly (depending on CPU), and only a few mil secs improvement when wrapping float4 in a struct. So, it's not my ideal situation, but I wouldn't mind at all having to use core.simd vector types directly, and moving things like dot/cross/normalize/etc to external functions, but if that's the case then I would _really_ like some basic usability features added to the vector types. working with them is much nicer than using D's core.simd vectors.I think core.simd is only designed for the lowest level of access to the SIMD hardware. I started writing std.simd some time back; it is mostly finished in a fork, but there are some bugs/missing features in D's SIMD support preventing me from finishing/releasing it. (incomplete dmd implementation, missing intrinsics, no SIMD literals, can't do unit testing, etc) The intention was that std.simd would be flat C-style api, which would be the lowest level required for practical and portable use. It's almost done, and it should make it a lot easier for people to build their own SIMD libraries on top. It supplies most useful linear algebraic operations, and implements them as efficiently as possible for other architectures than just SSE. Take a look: https://github.com/TurkeyMan/phobos/blob/master/std/simd.d On a side note, your example where you're performing a scalar add within a vector; this is bad, don't ever do this. SSE (ie, x86) is the most tolerant architecture in this regard, but it's VERY bad SIMD design. You should never perform any component-wise arithmetic when working with SIMD; It's absolutely not portable. Basically, a good rule of thumb is, if the keyword 'float' appears anywhere that interacts with your SIMD code, you are likely to see worse performance than just using float[4] on most architectures. Better to factor your code to eliminate any scalar work, and make sure 'scalars' are broadcast across all 4 components and continue doing 4d operations. Instead of: property pure nothrow float x(float4 v) { return v.ptr[0]; } Better to use: property pure nothrow float4 x(float4 v) { return swizzle!"xxxx"(v); }
Aug 06 2012
The intention was that std.simd would be flat C-style api, which would be the lowest level required for practical and portable use.Since LDC and GDC implement intrinsics with an API different from that used in DMD, there are actually two kinds of portability we need to worry about - portability across different compilers and portability across different architectures. std.simd solves both of those problems, which is great for many use cases (for example when dealing with geometric vectors), but it doesn't help when you want to use architecture dependant functionality directly. In this case one would want to have an interface as close to the actual instructions as possible but uniform across compilers. I think we should define such an interface as functions and templates in core.simd, so you would have for example: float4 unpcklps(float4, float4); float4 shufps(int, int, int, int)(float4, float4); Then each compiler would implement this API in its own way. DMD would use __simd (1), gdc would use GCC builtins and LDC would use LLVM intrinsics and shufflevector. If we don't include something like that in core.simd, many applications will need to implement their own versions of it. Using this would also reduce the amount of code needed to implement std.simd (currently most of the std.simd only supports GDC and it's already pretty large). What do you think about adding such an API to core.simd? (1) Some way to support the rest of SSE instructions needs to be added to DMD, of course.
Aug 06 2012
On 6 August 2012 22:57, jerro <a a.com> wrote:The intention was that std.simd would be flat C-style api, which would beI can see your reasoning, but I think that should be in core.sse, or core.simd.sse personally. Or you'll end up with VMX, NEON, etc all blobbed in one huge intrinsic wrapper file. That said, almost all simd opcodes are directly accessible in std.simd. There are relatively few obscure operations that don't have a representing function. The unpck/shuf example above for instance, they both effectively perform a sort of swizzle, and both are accessible through swizzle!(). The swizzle mask is analysed by the template, and it produces the best opcode to match the pattern. Take a look at swizzle, it's bloody complicated to do that the most efficient way on x86. Other architectures are not so much trouble ;) So while you may argue that it might be simpler to use an opcode intrinsic wrapper directly, the opcode is actually still directly accessible via swizzle and an appropriate swizzle arrangement, which it might also be argues is more readable to the end user, since the result of the opcode is clearly written... Then each compiler would implement this API in its own way. DMD would usethe lowest level required for practical and portable use.Since LDC and GDC implement intrinsics with an API different from that used in DMD, there are actually two kinds of portability we need to worry about - portability across different compilers and portability across different architectures. std.simd solves both of those problems, which is great for many use cases (for example when dealing with geometric vectors), but it doesn't help when you want to use architecture dependant functionality directly. In this case one would want to have an interface as close to the actual instructions as possible but uniform across compilers. I think we should define such an interface as functions and templates in core.simd, so you would have for example: float4 unpcklps(float4, float4); float4 shufps(int, int, int, int)(float4, float4);__simd (1), gdc would use GCC builtins and LDC would use LLVM intrinsics and shufflevector. If we don't include something like that in core.simd, many applications will need to implement their own versions of it. Using this would also reduce the amount of code needed to implement std.simd (currently most of the std.simd only supports GDC and it's already pretty large). What do you think about adding such an API to core.simd? (1) Some way to support the rest of SSE instructions needs to be added to DMD, of course.The reason I didn't write the DMD support yet is because it was incomplete, and many opcodes weren't yet accessible, like shuf for instance... and I just wasn't finished. Stopped to wait for DMD to be feature complete. I'm not opposed to this idea, although I do have a concern that, because there's no __forceinline in D (or macros), adding another layer of abstraction will make maths code REALLY slow in unoptimised builds. Can you suggest a method where these would be treated as C macros, and not produce additional layers of function calls? I'm already unhappy that std.simd produces redundant function calls. <rant> please please please can haz __forceinline! </rant>
Aug 07 2012
I can see your reasoning, but I think that should be in core.sse, or core.simd.sse personally. Or you'll end up with VMX, NEON, etc all blobbed in one huge intrinsic wrapper file.I would be okay with core.simd.sse or core.sse.That said, almost all simd opcodes are directly accessible in std.simd. There are relatively few obscure operations that don't have a representing function. The unpck/shuf example above for instance, they both effectively perform a sort of swizzle, and both are accessible through swizzle!().They aren't. Swizzle only takes one argument, so you cant use it to select elements from two vectors. Both unpcklps and shufps take two arguments. Writing a swizzle with two arguments would be much harder.The swizzle mask is analysed by the template, and it produces the best opcode to match the pattern. Take a look at swizzle, it's bloody complicated to do that the most efficient way on x86.Now imagine how complicated it would be to write a swizzle with to vector arguments.The reason I didn't write the DMD support yet is because it was incomplete, and many opcodes weren't yet accessible, like shuf for instance... and I just wasn't finished. Stopped to wait for DMD to be feature complete. I'm not opposed to this idea, although I do have a concern that, because there's no __forceinline in D (or macros), adding another layer of abstraction will make maths code REALLY slow in unoptimised builds. Can you suggest a method where these would be treated as C macros, and not produce additional layers of function calls?Unfortunately I can't, at least not a clean one. Using string mixins would be one way but I think no one wants that kind of API in Druntime or Phobos.I'm already unhappy that std.simd produces redundant function calls. <rant> please please please can haz __forceinline! </rant>I agree that we need that.
Aug 07 2012
On 7 August 2012 16:56, jerro <a a.com> wrote:That said, almost all simd opcodes are directly accessible in std.simd.Any usages I've missed/haven't thought of; I'm all ears. The swizzleThere are relatively few obscure operations that don't have a representing function. The unpck/shuf example above for instance, they both effectively perform a sort of swizzle, and both are accessible through swizzle!().They aren't. Swizzle only takes one argument, so you cant use it to select elements from two vectors. Both unpcklps and shufps take two arguments. Writing a swizzle with two arguments would be much harder.I can imagine, I'll have a go at it... it's something I considered, but not all architectures can do it efficiently. That said, a most-efficient implementation would probably still be useful on all architectures, but for cross platform code, I usually prefer to encourage people taking another approach rather than supply a function that is not particularly portable (or not efficient when ported). The reason I didn't write the DMD support yet is because it was incomplete,mask is analysed by the template, and it produces the best opcode to match the pattern. Take a look at swizzle, it's bloody complicated to do that the most efficient way on x86.Now imagine how complicated it would be to write a swizzle with to vector arguments.Yeah, absolutely not. This is possibly the most compelling motivation behind a __forceinline mechanism that I've seen come up... ;) I'm already unhappy thatand many opcodes weren't yet accessible, like shuf for instance... and I just wasn't finished. Stopped to wait for DMD to be feature complete. I'm not opposed to this idea, although I do have a concern that, because there's no __forceinline in D (or macros), adding another layer of abstraction will make maths code REALLY slow in unoptimised builds. Can you suggest a method where these would be treated as C macros, and not produce additional layers of function calls?Unfortunately I can't, at least not a clean one. Using string mixins would be one way but I think no one wants that kind of API in Druntime or Phobos.Huzzah! :)std.simd produces redundant function calls. <rant> please please please can haz __forceinline! </rant>I agree that we need that.
Oct 02 2012
On Tuesday, 2 October 2012 at 08:17:33 UTC, Manu wrote:On 7 August 2012 16:56, jerro <a a.com> wrote:I don't think it is possible to think of all usages of this, but for every simd instruction there are valid usages. At least for writing pfft, I found shuffling two vectors very useful. For, example, I needed a function that takes a small, square, power of two number of elements stored in vectors and bit-reverses them - it rearanges them so that you can calculate the new index of each element by reversing bits of the old index (for 16 elements using 4 element vectors this can actually be done using std.simd.transpose, but for AVX it was more efficient to make this function work on 64 elements). There are other places in pfft where I need to select elements from two vectors (for example, here https://github.com/jerro/pfft/blob/sine-transform/pfft/avx_float.d#L141 is the platform specific code for AVX). I don't think this are the kind of things that should be implemented in std.simd. If you wanted to implement all such operations (for example bit reversing a small array) that somebody may find useful at some time, std.simd would need to be huge, and most of it would never be used.That said, almost all simd opcodes are directly accessible in std.simd.Any usages I've missed/haven't thought of; I'm all ears.There are relatively few obscure operations that don't have a representing function. The unpck/shuf example above for instance, they both effectively perform a sort of swizzle, and both are accessible through swizzle!().They aren't. Swizzle only takes one argument, so you cant use it to select elements from two vectors. Both unpcklps and shufps take two arguments. Writing a swizzle with two arguments would be much harder.I can imagine, I'll have a go at it... it's something I considered, but not all architectures can do it efficiently. That said, a most-efficient implementation would probably still be useful on all architectures, but for cross platform code, I usually prefer to encourage people taking another approach rather than supply a function that is not particularly portable (or not efficient when ported).One way to do it would be to do the following for every set of selected indices: go through all the two element one instruction operations, and check if any of them does exactly what you need, and use it if it does. Otherwise do something that will always work although it may not always be optimal. One option would be to use swizzle on both vectors to get each of the elements to their final index and then blend the two vectors together. For sse 1, 2 and 3 you would need to use xorps to blend them, so I guess this is one more place where you would need vector literals. Someone who knows which two element shuffling operations the platform supports could still write optimal platform specific (but portable across compilers) code this way and for others this would still be useful to some degree (the documentation should mention that it may not be very efficient, though). But I think that it would be better to have platform specific APIs for platform specific code, as I said earlier in this thread.Walter opposes this, right? I wonder how we could convince him. There's one more thing that I wanted to ask you. If I were to add LDC support to std.simd, should I just add version(LDC) blocks to all the functions? Sounds like a lot of duplicated code...Unfortunately I can't, at least not a clean one. Using string mixins would be one way but I think no one wants that kind of API in Druntime or Phobos.Yeah, absolutely not. This is possibly the most compelling motivation behind a __forceinline mechanism that I've seen come up... ;) I'm already unhappy thatHuzzah! :)std.simd produces redundant function calls. <rant> please please please can haz __forceinline! </rant>I agree that we need that.
Oct 02 2012
On 2 October 2012 13:49, jerro <a a.com> wrote:I don't think it is possible to think of all usages of this, but for every simd instruction there are valid usages. At least for writing pfft, I found shuffling two vectors very useful. For, example, I needed a function that takes a small, square, power of two number of elements stored in vectors and bit-reverses them - it rearanges them so that you can calculate the new index of each element by reversing bits of the old index (for 16 elements using 4 element vectors this can actually be done using std.simd.transpose, but for AVX it was more efficient to make this function work on 64 elements). There are other places in pfft where I need to select elements from two vectors (for example, here https://github.com/jerro/pfft/** blob/sine-transform/pfft/avx_**float.d#L141<https://github.com/jerro/pfft/blob/sine-transform/pfft avx_float.d#L141>is the platform specific code for AVX). I don't think this are the kind of things that should be implemented in std.simd. If you wanted to implement all such operations (for example bit reversing a small array) that somebody may find useful at some time, std.simd would need to be huge, and most of it would never be used.I was referring purely to your 2-vector swizzle idea (or useful high-level ideas in general). Not to hyper-context-specific functions :P I can imagine, I'll have a go at it... it's something I considered, but notYeah, I have some ideas. Some permutations are obvious, the worst-case fallback is also obvious, but there are a lot of semi-efficient in-between cases which could take a while to identify and test. It'll be a massive block of static-if code to be sure ;) Unfortunately I can't, at least not a clean one. Using string mixins wouldall architectures can do it efficiently. That said, a most-efficient implementation would probably still be useful on all architectures, but for cross platform code, I usually prefer to encourage people taking another approach rather than supply a function that is not particularly portable (or not efficient when ported).One way to do it would be to do the following for every set of selected indices: go through all the two element one instruction operations, and check if any of them does exactly what you need, and use it if it does. Otherwise do something that will always work although it may not always be optimal. One option would be to use swizzle on both vectors to get each of the elements to their final index and then blend the two vectors together. For sse 1, 2 and 3 you would need to use xorps to blend them, so I guess this is one more place where you would need vector literals. Someone who knows which two element shuffling operations the platform supports could still write optimal platform specific (but portable across compilers) code this way and for others this would still be useful to some degree (the documentation should mention that it may not be very efficient, though). But I think that it would be better to have platform specific APIs for platform specific code, as I said earlier in this thread.I just don't think he's seen solid undeniable cases where it's necessary. There's one more thing that I wanted to ask you. If I were to add LDCWalter opposes this, right? I wonder how we could convince him.be one way but I think no one wants that kind of API in Druntime or Phobos.Yeah, absolutely not. This is possibly the most compelling motivation behind a __forceinline mechanism that I've seen come up... ;) I'm already unhappy thatstd.simd produces redundant function calls.Huzzah! :)<rant> please please please can haz __forceinline! </rant>I agree that we need that.support to std.simd, should I just add version(LDC) blocks to all the functions? Sounds like a lot of duplicated code...Go for it. And yeah, just add another version(). I don't think it can be done without blatant duplication. Certainly not without __forceinline anyway, and even then I'd be apprehensive to trust the code-gen of intrinsics wrapped in inline wrappers. That file will most likely become a nightmarish bloated mess... but that's the point of libraries ;) .. It's best all that horrible munge-ing for different architectures/compilers is put in one place and tested thoroughly, than to not provide it and allow an infinite variety of different implementations to appear. What we may want to do in the future is to split the different compilers/architectures into readable sub-modules, and public include the appropriate one based on version logic from std.simd... but I wouldn't want to do that until the API has stabilised.
Oct 02 2012
On Tuesday, 2 October 2012 at 13:36:37 UTC, Manu wrote:On 2 October 2012 13:49, jerro <a a.com> wrote:My point was that those context specific functions can be implemented using a 2 vector swizzle. LLVM, for example, actually provides access to most vector shuffling instruction through "shufflevector", which is basically a 2 vector swizzle.I don't think it is possible to think of all usages of this, but for every simd instruction there are valid usages. At least for writing pfft, I found shuffling two vectors very useful. For, example, I needed a function that takes a small, square, power of two number of elements stored in vectors and bit-reverses them - it rearanges them so that you can calculate the new index of each element by reversing bits of the old index (for 16 elements using 4 element vectors this can actually be done using std.simd.transpose, but for AVX it was more efficient to make this function work on 64 elements). There are other places in pfft where I need to select elements from two vectors (for example, here https://github.com/jerro/pfft/** blob/sine-transform/pfft/avx_**float.d#L141<https://github.com/jerro/pfft/blob/sine-transform/pfft avx_float.d#L141>is the platform specific code for AVX). I don't think this are the kind of things that should be implemented in std.simd. If you wanted to implement all such operations (for example bit reversing a small array) that somebody may find useful at some time, std.simd would need to be huge, and most of it would never be used.I was referring purely to your 2-vector swizzle idea (or useful high-level ideas in general). Not to hyper-context-specific functions :P
Oct 02 2012
On 2 October 2012 23:52, jerro <a a.com> wrote:On Tuesday, 2 October 2012 at 13:36:37 UTC, Manu wrote:Yeah, I understand. And it's a good suggestion. I'll add support for 2-vector swizzling next time I'm working on it.On 2 October 2012 13:49, jerro <a a.com> wrote:My point was that those context specific functions can be implemented using a 2 vector swizzle. LLVM, for example, actually provides access to most vector shuffling instruction through "shufflevector", which is basically a 2 vector swizzle.I don't think it is possible to think of all usages of this, but for every simd instruction there are valid usages. At least for writing pfft, I found shuffling two vectors very useful. For, example, I needed a function that takes a small, square, power of two number of elements stored in vectors and bit-reverses them - it rearanges them so that you can calculate the new index of each element by reversing bits of the old index (for 16 elements using 4 element vectors this can actually be done using std.simd.transpose, but for AVX it was more efficient to make this function work on 64 elements). There are other places in pfft where I need to select elements from two vectors (for example, here https://github.com/jerro/pfft/****<https://github.com/jerro/pfft/**> blob/sine-transform/pfft/avx_****float.d#L141<https://github.** https://github.com/jerro/pfft/blob/sine-transform/pfft/avx_float.d#L141>>is the platform specific code for AVX). I don't think this are the kind of things that should be implemented in std.simd. If you wanted to implement all such operations (for example bit reversing a small array) that somebody may find useful at some time, std.simd would need to be huge, and most of it would never be used.I was referring purely to your 2-vector swizzle idea (or useful high-level ideas in general). Not to hyper-context-specific functions :P
Oct 02 2012
On Monday, 6 August 2012 at 15:15:30 UTC, Manu wrote:I think core.simd is only designed for the lowest level of access to the SIMD hardware. I started writing std.simd some time back; it is mostly finished in a fork, but there are some bugs/missing features in D's SIMD support preventing me from finishing/releasing it. (incomplete dmd implementation, missing intrinsics, no SIMD literals, can't do unit testing, etc)Yes, I found, and have been referring to, your std.simd library for awhile now. Even with your library having GDC only support AtM, it's been a help. Thank you.The intention was that std.simd would be flat C-style api, which would be the lowest level required for practical and portable use. It's almost done, and it should make it a lot easier for people to build their own SIMD libraries on top. It supplies most useful linear algebraic operations, and implements them as efficiently as possible for other architectures than just SSE. Take a look: https://github.com/TurkeyMan/phobos/blob/master/std/simd.dRight now I'm working with DMD on Linux x86_64. LDC doesn't support SIMD right now, and I haven't built GDC yet, so I can't do performance comparisons between the two. I really need to get around to setting up GDC, because I've always planned on using that as a "release compiler" for my code. The problem is, as I mentioned above, that performance of SIMD completely get's shot when wrapping a float4 into a struct, rather than using float4 directly. There are some places where (like matrices), where they do make a big impact, but I'm trying to find the best solution for general code. For instance my current math library looks like: struct Vector4 { float x, y, z, w; ... } struct Matrix4 { Vector4 x, y, z, w; ... } but I was planning on changing over to (something like): alias float4 Vector4; alias float4[4] Matrix4; So I could use the types directly and reap the performance gains. I'm currently doing this to both my D code (still in early have "compiler magic" vector types, but Mono's version gives me access to component channels and simple constructors I can use, so for user code (and types like the Matrix above, with internal vectors) it's very convenient and natural. D's simply isn't, and I'm not sure there's any ways around it since again, at least with DMD, performance is shot when I put it in a struct.On a side note, your example where you're performing a scalar add within a vector; this is bad, don't ever do this. SSE (ie, x86) is the most tolerant architecture in this regard, but it's VERY bad SIMD design. You should never perform any component-wise arithmetic when working with SIMD; It's absolutely not portable. Basically, a good rule of thumb is, if the keyword 'float' appears anywhere that interacts with your SIMD code, you are likely to see worse performance than just using float[4] on most architectures. Better to factor your code to eliminate any scalar work, and make sure 'scalars' are broadcast across all 4 components and continue doing 4d operations. Instead of: property pure nothrow float x(float4 v) { return v.ptr[0]; } Better to use: property pure nothrow float4 x(float4 v) { return swizzle!"xxxx"(v); }Thanks a lot for telling me this, I don't know much about SIMD stuff. You're actually the exact person I wanted to talk to, because you do know a lot about this and I've always respected your opinions. I'm not apposed to doing something like: float4 addX(ref float4 v, float val) { float4 f; f.x = val v += f; } to do single component scalars, but it's very inconvenient for users to remember to use: vec.addX(scalar); instead of: vec.x += scalar; But that wouldn't be an issue if I could write custom operators for the components what basically did that. But I can't without wrapping float, which is why I am requesting these magic types get some basic features like that. I'm wondering if I should be looking at just using inlined ASM and use the ASM SIMD instructions directly. I know basic ASM, but I don't know what the potential pitfalls of doing that, especially with portability. Is there a reason not to do this (short of complexity)? I'm also wondering why wrapping a core.simd type into a struct completely negates performance.. I'm guessing because when I return the struct type, the compiler has to think about it as a struct, instead of it's "magic" type and all struct types have a bit more overhead. SIMD, by a factor of 8x usually on simple vector types (micro-benchmarks), and that's not counting the runtimes startup times either. However, when I use Mono.Simd, both DMD (with without the SGen GC or LLVM codegen) than it does on Window 8 with MS .NET, which I find to be pretty impressive and encouraging for our future games with Mono on Android (which has been out biggest performance PITA platform so far). I've noticed some really odd things with core.simd as well, which is another reason I'm thing of trying inlined ASM. I'm not sure what's causing certain compiler optimizations. For instance, given the basic test program, when I do: float rand = ...; // user input value float4 a, b = [1, 4, -12, 5]; a.ptr[0] = rand; a.ptr[1] = rand + 1; a.ptr[2] = rand + 2; a.ptr[3] = rand + 3; ulong mil; StopWatch sw; foreach (t; 0 .. testCount) { sw.start(); foreach (i; 0 .. 1_000_000) { a += b; b -= a; } sw.stop(); mil += sw.peek().msecs; sw.reset(); } writeln(a.array, ", ", b.array); writeln(cast(double) mil / testCount); When I run this on my Phenom II X4 920, it completes in ~9ms. For identical code. However, if I add: auto vec4(float x, float y, float z, float w) { float4 result; result.ptr[0] = x; result.ptr[1] = y; result.ptr[2] = z; result.ptr[3] = w; return result; } then replace the vector initialization lines: float4 a, b = [ ... ]; a.ptr[0] = rand; ... with ones using my factory function: auto a = vec4(rand, rand+1, rand+2, rand+3); auto b = vec4(1, 4, -12, 5); Then the program consistently completes in 2.15ms... wtf right? The printed vector output is identical, and there's no changes to the loop code (a += b, etc), I just change the construction code of the vectors and it runs 4.5x faster. Beats me, but I'll take it. Btw, for comparison, if I use a struct with an internal float4 it runs in ~19ms, and a struct with four floats runs in ~22ms. So you can see my concerns with using core.simd types directly, especially when my Intel Mac gets even better improvements with SIMD code. I haven't done extensive test on the Intel, but my original test using a struct with internal float4, and ~5ms for using float4 directly. anyways, thanks for the feedback.
Aug 06 2012
F i L wrote:SIMD, by a factor of 8x usually on simple vector types...Excuse me, this should have said a factor of 4x, not 8x.
Aug 06 2012
On 7 August 2012 04:24, F i L <witte2008 gmail.com> wrote:Right now I'm working with DMD on Linux x86_64. LDC doesn't support SIMD right now, and I haven't built GDC yet, so I can't do performance comparisons between the two. I really need to get around to setting up GDC, because I've always planned on using that as a "release compiler" for my code. The problem is, as I mentioned above, that performance of SIMD completely get's shot when wrapping a float4 into a struct, rather than using float4 directly. There are some places where (like matrices), where they do make a big impact, but I'm trying to find the best solution for general code. For instance my current math library looks like: struct Vector4 { float x, y, z, w; ... } struct Matrix4 { Vector4 x, y, z, w; ... } but I was planning on changing over to (something like): alias float4 Vector4; alias float4[4] Matrix4; So I could use the types directly and reap the performance gains. I'm code for Mono. Both core.simd and Mono.Simd have "compiler magic" vector types, but Mono's version gives me access to component channels and simple constructors I can use, so for user code (and types like the Matrix above, with internal vectors) it's very convenient and natural. D's simply isn't, and I'm not sure there's any ways around it since again, at least with DMD, performance is shot when I put it in a struct.I'm not sure why the performance would suffer when placing it in a struct. I suspect it's because the struct causes the vectors to become unaligned, and that impacts performance a LOT. Walter has recently made some changes to expand the capability of align() to do most of the stuff you expect should be possible, including aligning structs, and propogating alignment from a struct member to its containing struct. This change might actually solve your problems... Another suggestion I might make, is to write DMD intrinsics that mirror the GDC code in std.simd and use that, then I'll sort out any performance problems as soon as I have all the tools I need to finish the module :) There's nothing inherent in the std.simd api that will produce slower than optimal code when everything is working properly. Better to factor your code to eliminate any scalar work, and make sure'scalars' are broadcast across all 4 components and continue doing 4d operations. Instead of: property pure nothrow float x(float4 v) { return v.ptr[0]; } Better to use: property pure nothrow float4 x(float4 v) { return swizzle!"xxxx"(v); }Thanks a lot for telling me this, I don't know much about SIMD stuff. You're actually the exact person I wanted to talk to, because you do know a lot about this and I've always respected your opinions. I'm not apposed to doing something like: float4 addX(ref float4 v, float val) { float4 f; f.x = val v += f; }to do single component scalars, but it's very inconvenient for users to remember to use: vec.addX(scalar); instead of: vec.x += scalar;And this is precisely what I suggest you don't do. x64-SSE is the only architecture that can reasonably tolerate this (although it's still not the most efficient way). So if portability is important, you need to find another way. A 'proper' way to do this is something like: float4 wideScalar = loadScalar(scalar); // this function loads a float into all 4 components. Note: this is a little slow, factor these float->vector loads outside the hot loops as is practical. float4 vecX = getX(vec); // we can make shorthand for this, like 'vec.xxxx' for instance... vecX += wideScalar; // all 4 components maintain the same scalar value, this is so you can apply them back to non-scalar vectors later: With this, there are 2 typical uses, one is to scale another vector by your scalar, for instance: someOtherVector *= vecX; // perform a scale of a full 4d vector by our 'wide' scalar The other, less common operation, is that you may want to directly set the scalar to a component of another vector, setting Y to lock something to a height map for instance: someOtherVector = setY(someOtherVector, wideScalar); // note: it is still important that you have a 'wide' scalar in this case for portability, since different architectures have very different interleave operations. Something like '.x' can never appear in efficient code. Sadly, most modern SIMD hardware is simply not able to efficiently express what you as a programmer intuitively want as convenient operations. Most SIMD hardware has absolutely no connection between the FPU and the SIMD unit, resulting in loads and stores to memory, and this in turn introduces another set of performance hazards. x64 is actually the only architecture that does allow interaction between the FPU and SIMD however, although it's still no less efficient to do it how I describe, and as a bonus, your code will be portable. But that wouldn't be an issue if I could write custom operators for thecomponents what basically did that. But I can't without wrapping float, which is why I am requesting these magic types get some basic features like that.See above. I'm wondering if I should be looking at just using inlined ASM and use theASM SIMD instructions directly. I know basic ASM, but I don't know what the potential pitfalls of doing that, especially with portability. Is there a reason not to do this (short of complexity)? I'm also wondering why wrapping a core.simd type into a struct completely negates performance.. I'm guessing because when I return the struct type, the compiler has to think about it as a struct, instead of it's "magic" type and all struct types have a bit more overhead.Inline asm is usually less efficient for large blocks of code, it requires that you hand-tune the opcode sequencing, which is very hard to do, particularly for SSE. Small inline asm blocks are also usually less efficient, since most compilers can't rearrange other code within the function around the asm block, this leads to poor opcode sequencing. I recommend avoiding inline asm where performance is desired unless you're confident in writing the ENTIRE function/loop in asm, and hand tuning the opcode sequencing. But that's not portable...factor of 8x usually on simple vector types (micro-benchmarks), and that's not counting the runtimes startup times either. However, when I use (even without the SGen GC or LLVM codegen) than it does on Window 8 with MS .NET, which I find to be pretty impressive and encouraging for our future games with Mono on Android (which has been out biggest performance PITA platform so far).Android? But you're benchmarking x64-SSE right? I don't think it's reasonable to expect that performance characteristics for one architectures SIMD hardware will be any indicator at all of how another architecture may perform. Also, if you're doing any of the stuff I've been warning against above, NEON will suffer very hard, whereas x64-SSE will mostly shrug it off. I'm very interested to hear your measurements when you try it out! I've noticed some really odd things with core.simd as well, which isanother reason I'm thing of trying inlined ASM. I'm not sure what's causing certain compiler optimizations. For instance, given the basic test program, when I do: float rand = ...; // user input value float4 a, b = [1, 4, -12, 5]; a.ptr[0] = rand; a.ptr[1] = rand + 1; a.ptr[2] = rand + 2; a.ptr[3] = rand + 3; ulong mil; StopWatch sw; foreach (t; 0 .. testCount) { sw.start(); foreach (i; 0 .. 1_000_000) { a += b; b -= a; } sw.stop(); mil += sw.peek().msecs; sw.reset(); } writeln(a.array, ", ", b.array); writeln(cast(double) mil / testCount); When I run this on my Phenom II X4 920, it completes in ~9ms. For code. However, if I add: auto vec4(float x, float y, float z, float w) { float4 result; result.ptr[0] = x; result.ptr[1] = y; result.ptr[2] = z; result.ptr[3] = w; return result; } then replace the vector initialization lines: float4 a, b = [ ... ]; a.ptr[0] = rand; ... with ones using my factory function: auto a = vec4(rand, rand+1, rand+2, rand+3); auto b = vec4(1, 4, -12, 5); Then the program consistently completes in 2.15ms... wtf right? The printed vector output is identical, and there's no changes to the loop code (a += b, etc), I just change the construction code of the vectors and it runs 4.5x faster. Beats me, but I'll take it. Btw, for comparison, if I use a struct with an internal float4 it runs in ~19ms, and a struct with four floats runs in ~22ms. So you can see my concerns with using core.simd types directly, especially when my Intel Mac gets even better improvements with SIMD code. I haven't done extensive test on the Intel, but my original test (the one with internal float4, and ~5ms for using float4 directly.wtf indeed! O_o Can you paste the disassembly? There should be no loads or stores in the loop, therefore it should be unaffected... but it obviously is, so the only thing I can imagine that could make a different in the inner loop like that is a change in alignment. Wrapping a vector in a struct will break the alignment, since, until recently, DMD didn't propagate aligned members outwards to the containing struct (which I think Walter fixed in 2.60??). I can tell you this though, as soon as DMDs SIMD support is able to do the missing stuff I need to complete std.simd, I shall do that, along with intensive benchmarks where I'll be scrutinising the code-gen very closely. I expect performance peculiarities like you are seeing will be found and fixed at that time...
Aug 07 2012
Manu wrote:I'm not sure why the performance would suffer when placing it in a struct. I suspect it's because the struct causes the vectors to become unaligned, and that impacts performance a LOT. Walter has recently made some changes to expand the capability of align() to do most of the stuff you expect should be possible, including aligning structs, and propogating alignment from a struct member to its containing struct. This change might actually solve your problems...I've tried all combinations with align() before and inside the struct, with no luck. I'm using DMD 2.060, so unless there's a new syntax I'm unaware of, I don't think it's been adjusted to fix any alignment issues with SIMD stuff. It would be great to be able to wrap float4 into a struct, but for now I've come up with an easy and understandable alternative using SIMD types directly.Another suggestion I might make, is to write DMD intrinsics that mirror the GDC code in std.simd and use that, then I'll sort out any performance problems as soon as I have all the tools I need to finish the module :)Sounds like a good idea. I'll try and keep my code inline with yours to make transitioning to it easier when it's complete.And this is precisely what I suggest you don't do. x64-SSE is the only architecture that can reasonably tolerate this (although it's still not the most efficient way). So if portability is important, you need to find another way. A 'proper' way to do this is something like: float4 wideScalar = loadScalar(scalar); // this function loads a float into all 4 components. Note: this is a little slow, factor these float->vector loads outside the hot loops as is practical. float4 vecX = getX(vec); // we can make shorthand for this, like 'vec.xxxx' for instance... vecX += wideScalar; // all 4 components maintain the same scalar value, this is so you can apply them back to non-scalar vectors later: With this, there are 2 typical uses, one is to scale another vector by your scalar, for instance: someOtherVector *= vecX; // perform a scale of a full 4d vector by our 'wide' scalar The other, less common operation, is that you may want to directly set the scalar to a component of another vector, setting Y to lock something to a height map for instance: someOtherVector = setY(someOtherVector, wideScalar); // note: it is still important that you have a 'wide' scalar in this case for portability, since different architectures have very different interleave operations. Something like '.x' can never appear in efficient code. Sadly, most modern SIMD hardware is simply not able to efficiently express what you as a programmer intuitively want as convenient operations. Most SIMD hardware has absolutely no connection between the FPU and the SIMD unit, resulting in loads and stores to memory, and this in turn introduces another set of performance hazards. x64 is actually the only architecture that does allow interaction between the FPU and SIMD however, although it's still no less efficient to do it how I describe, and as a bonus, your code will be portable.Okay, that makes a lot of sense and is inline with what I was reading last night about FPU/SSE assembly code. However I'm also a bit confused. At some point, like in your hightmap example, I'm going to need to do arithmetic work on single vector components. Is there some sort of SSE arithmetic/shuffle instruction which uses "masking" that I should use to isolate and manipulate components? If not, and manipulating components is just bad for performance reasons, then I've figured out a solution to my original concern. By using this code: property trusted pure nothrow { auto ref x(T:float4)(auto ref T v) { return v.ptr[0]; } auto ref y(T:float4)(auto ref T v) { return v.ptr[1]; } auto ref z(T:float4)(auto ref T v) { return v.ptr[2]; } auto ref w(T:float4)(auto ref T v) { return v.ptr[3]; } void x(T:float4)(ref float4 v, float val) { v.ptr[0] = val; } void y(T:float4)(ref float4 v, float val) { v.ptr[1] = val; } void z(T:float4)(ref float4 v, float val) { v.ptr[2] = val; } void w(T:float4)(ref float4 v, float val) { v.ptr[3] = val; } } I am able to perform arithmetic on single components: auto vec = Vectors.float4(x, y, 0, 1); // factory vec.x += scalar; // += components again, I'll abandon this approach if there's a better way to manipulate single components, like you mentioned above. I'm just not aware of how to do that using SSE instructions alone. I'll do more research, but would appreciate any insight you can give.Inline asm is usually less efficient for large blocks of code, it requires that you hand-tune the opcode sequencing, which is very hard to do, particularly for SSE. Small inline asm blocks are also usually less efficient, since most compilers can't rearrange other code within the function around the asm block, this leads to poor opcode sequencing. I recommend avoiding inline asm where performance is desired unless you're confident in writing the ENTIRE function/loop in asm, and hand tuning the opcode sequencing. But that's not portable...Yes, after a bit of messing around with and researching ASM yesterday, I came to the conclusion that they're not a good fit for this. DMD can't inline functions with ASM blocks right now anyways (although LDC can), which would kill any performance gains SSE brings I'd imagine. Plus, ASM is a pain in the ass. :-)Android? But you're benchmarking x64-SSE right? I don't think it's reasonable to expect that performance characteristics for one architectures SIMD hardware will be any indicator at all of how another architecture may perform.code on any platform besides Windows/WP7/Xbox, and since Android that upgrading our Vector libraries to use Mono.Simd should yield significant improvements there. I'm just learning about SSE and proper vector utilization. In out last game we actually used Vector3's everywhere :-V , which even we should have know not too, because you have to convert them to float4's anyways to pass them into shader constants... I'm guessing this was our main performance issue with SmartPhones.. ahh, oh well.Also, if you're doing any of the stuff I've been warning against above, NEON will suffer very hard, whereas x64-SSE will mostly shrug it off. I'm very interested to hear your measurements when you try it out!I'll let you know if changing over to proper Vector code makes huge changes.wtf indeed! O_o Can you paste the disassembly?I'm not sure how to do that with DMD. I remember GDC has a output-to-asm flag, but not DMD. Or is there an external tool you use to look at .o/.obj files?I can tell you this though, as soon as DMDs SIMD support is able to do the missing stuff I need to complete std.simd, I shall do that, along with intensive benchmarks where I'll be scrutinising the code-gen very closely. I expect performance peculiarities like you are seeing will be found and fixed at that time...For now I've come to terms with using core.simd.float4 types directly have create acceptable solutions to my original problems. But I'm glad to here that in the future I'll have more flexibility within my libraries.
Aug 07 2012
F i L wrote:Okay, that makes a lot of sense and is inline with what I was reading last night about FPU/SSE assembly code. However I'm also a bit confused. At some point, like in your hightmap example, I'm going to need to do arithmetic work on single vector components. Is there some sort of SSE arithmetic/shuffle instruction which uses "masking" that I should use to isolate and manipulate components? If not, and manipulating components is just bad for performance reasons, then I've figured out a solution to my original concern. By using this code: property trusted pure nothrow { auto ref x(T:float4)(auto ref T v) { return v.ptr[0]; } auto ref y(T:float4)(auto ref T v) { return v.ptr[1]; } auto ref z(T:float4)(auto ref T v) { return v.ptr[2]; } auto ref w(T:float4)(auto ref T v) { return v.ptr[3]; } void x(T:float4)(ref float4 v, float val) { v.ptr[0] = val; } void y(T:float4)(ref float4 v, float val) { v.ptr[1] = val; } void z(T:float4)(ref float4 v, float val) { v.ptr[2] = val; } void w(T:float4)(ref float4 v, float val) { v.ptr[3] = val; } } I am able to perform arithmetic on single components: auto vec = Vectors.float4(x, y, 0, 1); // factory vec.x += scalar; // += components again, I'll abandon this approach if there's a better way to manipulate single components, like you mentioned above. I'm just not aware of how to do that using SSE instructions alone. I'll do more research, but would appreciate any insight you can give.Okay, disregard this. I see you where talking about your function in std.simd (setY), and I'm referring to that for an example of the appropriate vector functions.
Aug 07 2012
On 8 August 2012 07:54, F i L <witte2008 gmail.com> wrote:F i L wrote:Okay, that makes a lot of sense and is inline with what I was reading last night about FPU/SSE assembly code. However I'm also a bit confused. At some point, like in your hightmap example, I'm going to need to do arithmetic work on single vector components. Is there some sort of SSE arithmetic/shuffle instruction which uses "masking" that I should use to isolate and manipulate components? If not, and manipulating components is just bad for performance reasons, then I've figured out a solution to my original concern. By using this code: property trusted pure nothrow { auto ref x(T:float4)(auto ref T v) { return v.ptr[0]; } auto ref y(T:float4)(auto ref T v) { return v.ptr[1]; } auto ref z(T:float4)(auto ref T v) { return v.ptr[2]; } auto ref w(T:float4)(auto ref T v) { return v.ptr[3]; } void x(T:float4)(ref float4 v, float val) { v.ptr[0] = val; } void y(T:float4)(ref float4 v, float val) { v.ptr[1] = val; } void z(T:float4)(ref float4 v, float val) { v.ptr[2] = val; } void w(T:float4)(ref float4 v, float val) { v.ptr[3] = val; } } I am able to perform arithmetic on single components: auto vec = Vectors.float4(x, y, 0, 1); // factory vec.x += scalar; // += components again, I'll abandon this approach if there's a better way to manipulate single components, like you mentioned above. I'm just not aware of how to do that using SSE instructions alone. I'll do more research, but would appreciate any insight you can give.Okay, disregard this. I see you where talking about your function in std.simd (setY), and I'm referring to that for an example of the appropriate vector functions._<
Oct 02 2012
On Wednesday, 8 August 2012 at 01:45:52 UTC, F i L wrote:I'm not sure how to do that with DMD. I remember GDC has a output-to-asm flag, but not DMD. Or is there an external tool you use to look at .o/.obj files?objdump, otool – depending on your OS. David
Aug 08 2012
David Nadlinger wrote:objdump, otool – depending on your OS.Hey, nice tools. Good to know, thanks! Manu: Here's the disassembly for my benchmark code earlier, isolated between StopWatch .start()/.stop() https://gist.github.com/3294283 Also, I noticed your std.simd.setY() function uses _blendps() op, but DMD's core.simd doesn't support this op (yet? It's there but commented out). Is there an alternative operation I can use for setY() ?
Aug 08 2012
Not to resurrect the dead, I just wanted to share an article I came across concerning SIMD with Manu.. http://www.gamasutra.com/view/feature/4248/designing_fast_crossplatform_simd_.php QUOTE: 1. Returning results by value By observing the intrisics interface a vector library must imitate that interface to maximize performance. Therefore, you must return the results by value and not by reference, as such: //correct inline Vec4 VAdd(Vec4 va, Vec4 vb) { return(_mm_add_ps(va, vb)); }; On the other hand if the data is returned by reference the interface will generate code bloat. The incorrect version below: //incorrect (code bloat!) inline void VAddSlow(Vec4& vr, Vec4 va, Vec4 vb) { vr = _mm_add_ps(va, vb); }; The reason you must return data by value is because the quad-word (128-bit) fits nicely inside one SIMD register. And one of the key factors of a vector library is to keep the data inside these registers as much as possible. By doing that, you avoid unnecessary loads and stores operations from SIMD registers to memory or FPU registers. When combining multiple vector operations the "returned by value" interface allows the compiler to optimize these loads and stores easily by minimizing SIMD to FPU or memory transfers. 2. Data Declared "Purely" Here, "pure data" is defined as data declared outside a "class" or "struct" by a simple "typedef" or "define". When I was researching various vector libraries before coding VMath, I observed one common pattern among all libraries I looked at during that time. In all cases, developers wrapped the basic quad-word type inside a "class" or "struct" instead of declaring it purely, as follows: class Vec4 { ... private: __m128 xyzw; }; This type of data encapsulation is a common practice among C++ developers to make the architecture of the software robust. The data is protected and can be accessed only by the class interface functions. Nonetheless, this design causes code bloat by many different compilers in different platforms, especially if some sort of GCC port is being used. An approach that is much friendlier to the compiler is to declare the vector data "purely", as follows: typedef __m128 Vec4; ENDQUOTE; The article is 2 years old, but It appears my earlier performance issue wasn't D related at all, but an issue with C as well. I think in this situation, it might be best (most optimized) to handle simd "the C way" by creating and alias or union of a simd intrinsic. D has a big advantage over C/C++ here because of UFCS, in that we can write external functions that appear no different to encapsulated object methods. That combined with public-aliasing means the end-user only sees our pretty functions, but we're not sacrificing performance at all.
Oct 01 2012
On 02-Oct-12 06:28, F i L wrote:D has a big advantage over C/C++ here because of UFCS, in that we can write external functions that appear no different to encapsulated object methods. That combined with public-aliasing means the end-user only sees our pretty functions, but we're not sacrificing performance at all.Yeah, but it won't cover operators. If only opBinary could be defined at global scope... I think I've seen an enhancement to that end though. But even then simd types are built-in and operator overloading only works with user-defined types. -- Dmitry Olshansky
Oct 01 2012
On 2 October 2012 05:28, F i L <witte2008 gmail.com> wrote:Not to resurrect the dead, I just wanted to share an article I came across concerning SIMD with Manu.. http://www.gamasutra.com/view/**feature/4248/designing_fast_** crossplatform_simd_.php<http://www.gamasutra.com/view/feature/4248/designing_fast_crossplatform_simd_.php> QUOTE: 1. Returning results by value By observing the intrisics interface a vector library must imitate that interface to maximize performance. Therefore, you must return the results by value and not by reference, as such: //correct inline Vec4 VAdd(Vec4 va, Vec4 vb) { return(_mm_add_ps(va, vb)); }; On the other hand if the data is returned by reference the interface will generate code bloat. The incorrect version below: //incorrect (code bloat!) inline void VAddSlow(Vec4& vr, Vec4 va, Vec4 vb) { vr = _mm_add_ps(va, vb); }; The reason you must return data by value is because the quad-word (128-bit) fits nicely inside one SIMD register. And one of the key factors of a vector library is to keep the data inside these registers as much as possible. By doing that, you avoid unnecessary loads and stores operations from SIMD registers to memory or FPU registers. When combining multiple vector operations the "returned by value" interface allows the compiler to optimize these loads and stores easily by minimizing SIMD to FPU or memory transfers. 2. Data Declared "Purely" Here, "pure data" is defined as data declared outside a "class" or "struct" by a simple "typedef" or "define". When I was researching various vector libraries before coding VMath, I observed one common pattern among all libraries I looked at during that time. In all cases, developers wrapped the basic quad-word type inside a "class" or "struct" instead of declaring it purely, as follows: class Vec4 { ... private: __m128 xyzw; }; This type of data encapsulation is a common practice among C++ developers to make the architecture of the software robust. The data is protected and can be accessed only by the class interface functions. Nonetheless, this design causes code bloat by many different compilers in different platforms, especially if some sort of GCC port is being used. An approach that is much friendlier to the compiler is to declare the vector data "purely", as follows: typedef __m128 Vec4; ENDQUOTE; The article is 2 years old, but It appears my earlier performance issue wasn't D related at all, but an issue with C as well. I think in this situation, it might be best (most optimized) to handle simd "the C way" by creating and alias or union of a simd intrinsic. D has a big advantage over C/C++ here because of UFCS, in that we can write external functions that appear no different to encapsulated object methods. That combined with public-aliasing means the end-user only sees our pretty functions, but we're not sacrificing performance at all.These are indeed common gotchas. But they don't necessarily apply to D, and if they do, then they should be bugged and hopefully addressed. There is no reason that D needs to follow these typical performance patterns from C. It's worth noting that not all C compilers suffer from this problem. There are many (most actually) compilers that can recognise a struct with a single member and treat it as if it were an instance of that member directly when being passed by value. It only tends to be a problem on older games-console compilers. As I said earlier. When I get back to finishing srd.simd off (I presume this will be some time after Walter has finished Win64 support), I'll go through and scrutinise the code-gen for the API very thoroughly. We'll see what that reveals. But I don't think there's any reason we should suffer the same legacy C by-value code-gen problems in D... (hopefully I won't eat those words ;)
Oct 02 2012
Manu wrote:These are indeed common gotchas. But they don't necessarily apply to D, and if they do, then they should be bugged and hopefully addressed. There is no reason that D needs to follow these typical performance patterns from C. It's worth noting that not all C compilers suffer from this problem. There are many (most actually) compilers that can recognise a struct with a single member and treat it as if it were an instance of that member directly when being passed by value. It only tends to be a problem on older games-console compilers. As I said earlier. When I get back to finishing srd.simd off (I presume this will be some time after Walter has finished Win64 support), I'll go through and scrutinise the code-gen for the API very thoroughly. We'll see what that reveals. But I don't think there's any reason we should suffer the same legacy C by-value code-gen problems in D... (hopefully I won't eat those words ;)Thanks for the insight (and the code examples, though I've been researching SIMD best-practice in C recently). It's good to know that D should (hopefully) be able to avoid these pitfalls. On a side note, I'm not sure how easy LLVM is to build on Windows (I think I built it once a long time ago), but recent performance comparisons between DMD, LDC, and GDC show that LDC (with LLVM 3.1 auto-vectorization and not using GCC -ffast-math) actually produces on-par-or-faster binary compared to GDC, at least in my code on Linux64. SIMD in LDC is currently broken, but you might consider using that if you're having trouble keeping a D release compiler up-to-date.
Oct 02 2012
SIMD in LDC is currently brokenWhat problems did you have with it? It seems to work fine for me.
Oct 02 2012
On Tuesday, 2 October 2012 at 21:03:36 UTC, jerro wrote:Can you post an example of doing a simple arithmetic with two 'float4's? My simple tests either fail with LLVM errors or don't produce correct results (which reminds me, I meant to report them, I'll do that). Here's an example: import core.simd, std.stdio; void main() { float4 a = 1, b = 2; writeln((a + b).array); // WORKS: [3, 3, 3, 3] float4 c = [1, 2, 3, 4]; // ERROR: "Stored value type does // not match pointer operand type!" // [..a bunch of LLVM error code..] float4 c = 0, d = 1; c.array[0] = 4; c.ptr[1] = 4; writeln((c + d).array); // WRONG: [1, 1, 1, 1] }SIMD in LDC is currently brokenWhat problems did you have with it? It seems to work fine for me.
Oct 02 2012
Also, I'm using the LDC off the official Arch community repo.
Oct 02 2012
import core.simd, std.stdio; void main() { float4 a = 1, b = 2; writeln((a + b).array); // WORKS: [3, 3, 3, 3] float4 c = [1, 2, 3, 4]; // ERROR: "Stored value type does // not match pointer operand type!" // [..a bunch of LLVM error code..] float4 c = 0, d = 1; c.array[0] = 4; c.ptr[1] = 4; writeln((c + d).array); // WRONG: [1, 1, 1, 1] }Oh, that doesn't work for me either. I never tried to use those, so I didn't notice that before. This code gives me internal compiler errors with GDC and DMD too (with "float4 c = [1, 2, 3, 4]" commented out). I'm using DMD 2.060 and a recent versions of GDC and LDC on 64 bit Linux.
Oct 02 2012
jerro wrote:This code gives me internal compiler errors with GDC and DMD too (with "float4 c = [1, 2, 3, 4]" commented out). I'm using DMD 2.060 and a recent versions of GDC and LDC on 64 bit Linux.Yes the SIMD situation isn't entirely usable right now with DMD and LDC. Only simple vector arithmetic is possible to my knowledge. The internal DMD error is actually from processing '(a + b)' and returning it to writeln() without assigning to an separate float4 first.. for example, this compiles with DMD and outputs correctly: import core.simd, std.stdio; void main() { float4 a = 1, b = 2; float4 r = a + b; writeln(r.array); float4 c = [1, 2, 3, 4]; float4 d = 1; c.array[0] = 4; c.ptr[1] = 4; r = c + d; writeln(r.array); } correctly outputs: [3, 3, 3, 3] [5, 5, 4, 5] I've never tried to do SIMD with GDC, though I understand it's done differently and core.simd XMM operations aren't supported (though I can't get them to work in DMD either... *sigh*). Take a look at Manu's std.simd library for reference on GDC SIMD support: https://github.com/TurkeyMan/phobos/blob/master/std/simd.d
Oct 02 2012
On 3 October 2012 02:31, jerro <a a.com> wrote:Then don't just talk about it, raise a bug - otherwise how do you expect it to get fixed! ( http://www.gdcproject.org/bugzilla ) I've made a note of the error you get with `__vector(float[4]) c = [1,2,3,4];' - That is because vector expressions implementation is very basic at the moment. Look forward to hear from all your experiences so we can make vector support rock solid in GDC. ;-) -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';import core.simd, std.stdio; void main() { float4 a = 1, b = 2; writeln((a + b).array); // WORKS: [3, 3, 3, 3] float4 c = [1, 2, 3, 4]; // ERROR: "Stored value type does // not match pointer operand type!" // [..a bunch of LLVM error code..] float4 c = 0, d = 1; c.array[0] = 4; c.ptr[1] = 4; writeln((c + d).array); // WRONG: [1, 1, 1, 1] }Oh, that doesn't work for me either. I never tried to use those, so I didn't notice that before. This code gives me internal compiler errors with GDC and DMD too (with "float4 c = [1, 2, 3, 4]" commented out). I'm using DMD 2.060 and a recent versions of GDC and LDC on 64 bit Linux.
Oct 03 2012
Then don't just talk about it, raise a bug - otherwise how do you expect it to get fixed! ( http://www.gdcproject.org/bugzilla )I'm trying to create a bugzilla account on that site now, but account creation doesn't seem to be working (I never get the confirmation e-mail).
Oct 03 2012
jerro wrote:I'm trying to create a bugzilla account on that site now, but account creation doesn't seem to be working (I never get the confirmation e-mail).I never received an email either. Is there a expected time delay?
Oct 03 2012
On 3 October 2012 16:40, Iain Buclaw <ibuclaw ubuntu.com> wrote:On 3 October 2012 02:31, jerro <a a.com> wrote:I didn't realise vector literals like that were supported properly in the front end yet? Do they work at all? What does the code generated look like?didn'timport core.simd, std.stdio; void main() { float4 a = 1, b = 2; writeln((a + b).array); // WORKS: [3, 3, 3, 3] float4 c = [1, 2, 3, 4]; // ERROR: "Stored value type does // not match pointer operand type!" // [..a bunch of LLVM error code..] float4 c = 0, d = 1; c.array[0] = 4; c.ptr[1] = 4; writeln((c + d).array); // WRONG: [1, 1, 1, 1] }Oh, that doesn't work for me either. I never tried to use those, so Inotice that before. This code gives me internal compiler errors with GDCandDMD too (with "float4 c = [1, 2, 3, 4]" commented out). I'm using DMD2.060and a recent versions of GDC and LDC on 64 bit Linux.Then don't just talk about it, raise a bug - otherwise how do you expect it to get fixed! ( http://www.gdcproject.org/bugzilla ) I've made a note of the error you get with `__vector(float[4]) c = [1,2,3,4];' - That is because vector expressions implementation is very basic at the moment. Look forward to hear from all your experiences so we can make vector support rock solid in GDC. ;-)
Oct 05 2012
On 5 October 2012 11:28, Manu <turkeyman gmail.com> wrote:On 3 October 2012 16:40, Iain Buclaw <ibuclaw ubuntu.com> wrote:They get passed to the backend as of 2.060 - so looks like the semantic passes now allow them. I've just recently added backend support in GDC - https://github.com/D-Programming-GDC/GDC/commit/7ada3d95b8af1b271d82f1ec5208f0b689eb143c#L1R1194 The codegen looks like so: float4 a = 2; float4 b = [1,2,3,4]; ==> vector(4) float a = { 2.0e+0, 2.0e+0, 2.0e+0, 2.0e+0 }; vector(4) float b = { 1.0e+0, 2.0e+0, 3.0e+0, 4.0e+0 }; ==> movaps .LC0, %xmm0 movaps %xmm0, -24(%ebp) movaps .LC1, %xmm0 movaps %xmm0, -40(%ebp) .align 16 .LC0: .long 1073741824 .long 1073741824 .long 1073741824 .long 1073741824 .align 16 .LC1: .long 1065353216 .long 1073741824 .long 1077936128 .long 1082130432 Regards, -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';On 3 October 2012 02:31, jerro <a a.com> wrote:I didn't realise vector literals like that were supported properly in the front end yet? Do they work at all? What does the code generated look like?Then don't just talk about it, raise a bug - otherwise how do you expect it to get fixed! ( http://www.gdcproject.org/bugzilla ) I've made a note of the error you get with `__vector(float[4]) c = [1,2,3,4];' - That is because vector expressions implementation is very basic at the moment. Look forward to hear from all your experiences so we can make vector support rock solid in GDC. ;-)import core.simd, std.stdio; void main() { float4 a = 1, b = 2; writeln((a + b).array); // WORKS: [3, 3, 3, 3] float4 c = [1, 2, 3, 4]; // ERROR: "Stored value type does // not match pointer operand type!" // [..a bunch of LLVM error code..] float4 c = 0, d = 1; c.array[0] = 4; c.ptr[1] = 4; writeln((c + d).array); // WRONG: [1, 1, 1, 1] }Oh, that doesn't work for me either. I never tried to use those, so I didn't notice that before. This code gives me internal compiler errors with GDC and DMD too (with "float4 c = [1, 2, 3, 4]" commented out). I'm using DMD 2.060 and a recent versions of GDC and LDC on 64 bit Linux.
Oct 05 2012
On 5 October 2012 14:46, Iain Buclaw <ibuclaw ubuntu.com> wrote:On 5 October 2012 11:28, Manu <turkeyman gmail.com> wrote:Perfect! I can get on with my unittests :POn 3 October 2012 16:40, Iain Buclaw <ibuclaw ubuntu.com> wrote:GDCOn 3 October 2012 02:31, jerro <a a.com> wrote:import core.simd, std.stdio; void main() { float4 a = 1, b = 2; writeln((a + b).array); // WORKS: [3, 3, 3, 3] float4 c = [1, 2, 3, 4]; // ERROR: "Stored value type does // not match pointer operand type!" // [..a bunch of LLVM error code..] float4 c = 0, d = 1; c.array[0] = 4; c.ptr[1] = 4; writeln((c + d).array); // WRONG: [1, 1, 1, 1] }Oh, that doesn't work for me either. I never tried to use those, so I didn't notice that before. This code gives me internal compiler errors withThey get passed to the backend as of 2.060 - so looks like the semantic passes now allow them. I've just recently added backend support in GDC - https://github.com/D-Programming-GDC/GDC/commit/7ada3d95b8af1b271d82f1ec5208f0b689eb143c#L1R1194 The codegen looks like so: float4 a = 2; float4 b = [1,2,3,4]; ==> vector(4) float a = { 2.0e+0, 2.0e+0, 2.0e+0, 2.0e+0 }; vector(4) float b = { 1.0e+0, 2.0e+0, 3.0e+0, 4.0e+0 }; ==> movaps .LC0, %xmm0 movaps %xmm0, -24(%ebp) movaps .LC1, %xmm0 movaps %xmm0, -40(%ebp) .align 16 .LC0: .long 1073741824 .long 1073741824 .long 1073741824 .long 1073741824 .align 16 .LC1: .long 1065353216 .long 1073741824 .long 1077936128 .long 1082130432I didn't realise vector literals like that were supported properly in the front end yet? Do they work at all? What does the code generated look like?and DMD too (with "float4 c = [1, 2, 3, 4]" commented out). I'm using DMD 2.060 and a recent versions of GDC and LDC on 64 bit Linux.Then don't just talk about it, raise a bug - otherwise how do you expect it to get fixed! ( http://www.gdcproject.org/bugzilla ) I've made a note of the error you get with `__vector(float[4]) c = [1,2,3,4];' - That is because vector expressions implementation is very basic at the moment. Look forward to hear from all your experiences so we can make vector support rock solid in GDC. ;-)
Oct 07 2012
On Sunday, 7 October 2012 at 12:24:48 UTC, Manu wrote:Perfect! I can get on with my unittests :PSpeaking of test – are they available somewhere? Now that LDC at least theoretically supports most of the GCC builtins, I'd like to throw some tests at it to see what happens. David
Oct 14 2012
On 14 October 2012 21:05, David Nadlinger <see klickverbot.at> wrote:On Sunday, 7 October 2012 at 12:24:48 UTC, Manu wrote:Could you pastebin a header generation of the gccbuiltins module? We can compare. =3D) --=20 Iain Buclaw *(p < e ? p++ : p) =3D (c & 0x0f) + '0';Perfect! I can get on with my unittests :PSpeaking of test =96 are they available somewhere? Now that LDC at least theoretically supports most of the GCC builtins, I'd like to throw some tests at it to see what happens. David
Oct 14 2012
On 14 October 2012 21:58, Iain Buclaw <ibuclaw ubuntu.com> wrote:On 14 October 2012 21:05, David Nadlinger <see klickverbot.at> wrote:http://dpaste.dzfl.pl/4edb9ecc --=20 Iain Buclaw *(p < e ? p++ : p) =3D (c & 0x0f) + '0';On Sunday, 7 October 2012 at 12:24:48 UTC, Manu wrote:Could you pastebin a header generation of the gccbuiltins module? We can compare. =3D)Perfect! I can get on with my unittests :PSpeaking of test =96 are they available somewhere? Now that LDC at least theoretically supports most of the GCC builtins, I'd like to throw some tests at it to see what happens. David
Oct 14 2012
Speaking of test – are they available somewhere? Now that LDC at least theoretically supports most of the GCC builtins, I'd like to throw some tests at it to see what happens. DavidI have a fork of std.simd with LDC support at https://github.com/jerro/phobos/tree/std.simd and some tests for it at https://github.com/jerro/std.simd-tests .
Oct 14 2012
On 15 October 2012 02:50, jerro <a a.com> wrote:Speaking of test =E2=80=93 are they available somewhere? Now that LDC at =leastthub.com/jerro/std.simd-tests>.theoretically supports most of the GCC builtins, I'd like to throw some tests at it to see what happens. DavidI have a fork of std.simd with LDC support at https://github.com/jerro/** phobos/tree/std.simd <https://github.com/jerro/phobos/tree/std.simd> and some tests for it at https://github.com/jerro/std.**simd-tests<https://gi=Awesome. Pull request plz! :) That said, how did you come up with a lot of these implementations? Some don't look particularly efficient, and others don't even look right. xor for instance: return cast(T) (cast(int4) v1 ^ cast(int4) v2); This is wrong for float types. x86 has separate instructions for doing this to floats, which make sure to do the right thing by the flags registers. Most of the LDC blocks assume that it could be any architecture... I don't think this will produce good portable code. It needs to be much more cafully hand-crafted, but it's a nice working start.
Oct 15 2012
On Monday, 15 October 2012 at 12:19:47 UTC, Manu wrote:On 15 October 2012 02:50, jerro <a a.com> wrote:I did change an API for a few functions like loadUnaligned, though. In those cases the signatures needed to be changed because the functions used T or T* for scalar parameters and return types and Vector!T for the vector parameters and return types. This only compiles if T is a static array which I don't think makes much sense. I changed those to take the vector type as a template parameter. The vector type can not be inferred from the scalar type because you can use vector registers of different sizes simultaneously (with AVX, for example). Because of that the vector type must be passed explicitly for some functions, so I made it the first template parameter in those cases, so that Ver doesn't always need to be specified. There is one more issue that I need to solve (and that may be a problem in some cases with GDC too) - the pure, safe and nothrow attributes. Currently gcc builtin declarations in LDC have none of those attributes (I have to look into which of those can be added and if it can be done automatically). I've just commented out the attributes in my std.simd fork for now, but this isn't a proper solution.Speaking of test – are they available somewhere? Now that LDC at leastAwesome. Pull request plz! :)theoretically supports most of the GCC builtins, I'd like to throw some tests at it to see what happens. DavidI have a fork of std.simd with LDC support at https://github.com/jerro/** phobos/tree/std.simd <https://github.com/jerro/phobos/tree/std.simd> and some tests for it at https://github.com/jerro/std.**simd-tests<https://github.com/jerro/std.simd-tests>.That said, how did you come up with a lot of these implementations? Some don't look particularly efficient, and others don't even look right. xor for instance: return cast(T) (cast(int4) v1 ^ cast(int4) v2); This is wrong for float types. x86 has separate instructions for doing this to floats, which make sure to do the right thing by the flags registers. Most of the LDC blocks assume that it could be any architecture... I don't think this will produce good portable code. It needs to be much more cafully hand-crafted, but it's a nice working start.The problem is that LLVM doesn't provide intrinsics for those operations. The xor function does compile to a single xorps instruction when compiling with -O1 or higher, though. I have looked at the code generated for many (most, I think, but not for all possible types) of those LDC blocks and most of them compile to the appropriate single instruction when compiled with -O2 or -O3. Even the ones for which the D source code looks horribly inefficient like for example loadUnaligned. By the way, clang does those in a similar way. For example, here is what clang emits for a wrapper around _mm_xor_ps when compiled with -O1 -emit-llvm: define <4 x float> foo(<4 x float> %a, <4 x float> %b) nounwind uwtable readnone { %1 = bitcast <4 x float> %a to <4 x i32> %2 = bitcast <4 x float> %b to <4 x i32> %3 = xor <4 x i32> %1, %2 %4 = bitcast <4 x i32> %3 to <4 x float> ret <4 x float> %4 } AFAICT, the only way to ensure that a certain instruction will be used with LDC when there is no LLVM intrinsic for it is to use inline assembly expressions. I remember having some problems with those in the past, but it could be that I was doing something wrong. Maybe we should look into that option too.
Oct 15 2012
On 15 October 2012 16:34, jerro <a a.com> wrote:On Monday, 15 October 2012 at 12:19:47 UTC, Manu wrote:t leastOn 15 October 2012 02:50, jerro <a a.com> wrote: Speaking of test =E2=80=93 are they available somewhere? Now that LDC a=etheoretically supports most of the GCC builtins, I'd like to throw som=ttps://github.com/jerro/phobos/tree/std.simd>>tests at it to see what happens. DavidI have a fork of std.simd with LDC support at https://github.com/jerro/** phobos/tree/std.simd <https://github.com/jerro/**phobos/tree/std.simd<h=//github.com/jerro/std.**simd-tests>and some tests for it at https://github.com/jerro/std.****simd-tests<https:=.simd-tests><https://github.**com/jerro/std.simd-tests<https://github.com/jerro/std=dI did change an API for a few functions like loadUnaligned, though. In those cases the signatures needed to be changed because the functions use=Awesome. Pull request plz! :).T or T* for scalar parameters and return types and Vector!T for the vecto=rparameters and return types. This only compiles if T is a static array which I don't think makes much sense. I changed those to take the vector type as a template parameter. The vector type can not be inferred from th=escalar type because you can use vector registers of different sizes simultaneously (with AVX, for example). Because of that the vector type must be passed explicitly for some functions, so I made it the first template parameter in those cases, so that Ver doesn't always need to be specified. There is one more issue that I need to solve (and that may be a problem i=nsome cases with GDC too) - the pure, safe and nothrow attributes. Currently gcc builtin declarations in LDC have none of those attributes (=Ihave to look into which of those can be added and if it can be done automatically). I've just commented out the attributes in my std.simd for=kfor now, but this isn't a proper solution. That said, how did you come up with a lot of these implementations? Some'tdon't look particularly efficient, and others don't even look right. xor for instance: return cast(T) (cast(int4) v1 ^ cast(int4) v2); This is wrong for float types. x86 has separate instructions for doing this to floats, which make sure to do the right thing by the flags registers. Most of the LDC blocks assume that it could be any architecture... I don=gthink this will produce good portable code. It needs to be much more cafully hand-crafted, but it's a nice working start.The problem is that LLVM doesn't provide intrinsics for those operations. The xor function does compile to a single xorps instruction when compilin=with -O1 or higher, though. I have looked at the code generated for many (most, I think, but not for all possible types) of those LDC blocks and most of them compile to the appropriate single instruction when compiled with -O2 or -O3. Even the ones for which the D source code looks horribly inefficient like for example loadUnaligned. By the way, clang does those in a similar way. For example, here is what clang emits for a wrapper around _mm_xor_ps when compiled with -O1 -emit-llvm: define <4 x float> foo(<4 x float> %a, <4 x float> %b) nounwind uwtable readnone { %1 =3D bitcast <4 x float> %a to <4 x i32> %2 =3D bitcast <4 x float> %b to <4 x i32> %3 =3D xor <4 x i32> %1, %2 %4 =3D bitcast <4 x i32> %3 to <4 x float> ret <4 x float> %4 } AFAICT, the only way to ensure that a certain instruction will be used with LDC when there is no LLVM intrinsic for it is to use inline assembly expressions. I remember having some problems with those in the past, but =itcould be that I was doing something wrong. Maybe we should look into that option too.Inline assembly usually ruins optimising (code reordering around inline asm blocks is usually considered impossible). It's interesting that the x86 codegen makes such good sense of those sequences, but I'm rather more concerned about other platforms. I wonder if other platforms have a similarly incomplete subset of intrinsics? :/
Oct 15 2012
On Monday, 15 October 2012 at 13:43:28 UTC, Manu wrote:On 15 October 2012 16:34, jerro <a a.com> wrote:I don't see a reason why the compiler couldn't reorder code around GCC style inline assembly blocks. You are supposed to specify which registers are changed in the block. Doesn't that give the compiler enough information to reorder code?On Monday, 15 October 2012 at 12:19:47 UTC, Manu wrote:Inline assembly usually ruins optimising (code reordering around inline asm blocks is usually considered impossible).On 15 October 2012 02:50, jerro <a a.com> wrote: Speaking of test – are they available somewhere? Now that LDC at leastI did change an API for a few functions like loadUnaligned, though. In those cases the signatures needed to be changed because the functions used T or T* for scalar parameters and return types and Vector!T for the vector parameters and return types. This only compiles if T is a static array which I don't think makes much sense. I changed those to take the vector type as a template parameter. The vector type can not be inferred from the scalar type because you can use vector registers of different sizes simultaneously (with AVX, for example). Because of that the vector type must be passed explicitly for some functions, so I made it the first template parameter in those cases, so that Ver doesn't always need to be specified. There is one more issue that I need to solve (and that may be a problem in some cases with GDC too) - the pure, safe and nothrow attributes. Currently gcc builtin declarations in LDC have none of those attributes (I have to look into which of those can be added and if it can be done automatically). I've just commented out the attributes in my std.simd fork for now, but this isn't a proper solution. That said, how did you come up with a lot of these implementations? SomeAwesome. Pull request plz! :)theoretically supports most of the GCC builtins, I'd like to throw some tests at it to see what happens. DavidI have a fork of std.simd with LDC support at https://github.com/jerro/** phobos/tree/std.simd <https://github.com/jerro/**phobos/tree/std.simd<https://github.com/jerro/phobos/tree/std.simd>> and some tests for it at https://github.com/jerro/std.****simd-tests<https://github.com/jerro/std.**simd-tests> <https://github.**com/jerro/std.simd-tests<https://github.com/jerro/std.simd-tests>.don't look particularly efficient, and others don't even look right. xor for instance: return cast(T) (cast(int4) v1 ^ cast(int4) v2); This is wrong for float types. x86 has separate instructions for doing this to floats, which make sure to do the right thing by the flags registers. Most of the LDC blocks assume that it could be any architecture... I don't think this will produce good portable code. It needs to be much more cafully hand-crafted, but it's a nice working start.The problem is that LLVM doesn't provide intrinsics for those operations. The xor function does compile to a single xorps instruction when compiling with -O1 or higher, though. I have looked at the code generated for many (most, I think, but not for all possible types) of those LDC blocks and most of them compile to the appropriate single instruction when compiled with -O2 or -O3. Even the ones for which the D source code looks horribly inefficient like for example loadUnaligned. By the way, clang does those in a similar way. For example, here is what clang emits for a wrapper around _mm_xor_ps when compiled with -O1 -emit-llvm: define <4 x float> foo(<4 x float> %a, <4 x float> %b) nounwind uwtable readnone { %1 = bitcast <4 x float> %a to <4 x i32> %2 = bitcast <4 x float> %b to <4 x i32> %3 = xor <4 x i32> %1, %2 %4 = bitcast <4 x i32> %3 to <4 x float> ret <4 x float> %4 } AFAICT, the only way to ensure that a certain instruction will be used with LDC when there is no LLVM intrinsic for it is to use inline assembly expressions. I remember having some problems with those in the past, but it could be that I was doing something wrong. Maybe we should look into that option too.It's interesting that the x86 codegen makes such good sense of those sequences, but I'm rather more concerned about other platforms. I wonder if other platforms have a similarly incomplete subset of intrinsics? :/It looks to me like LLVM does provide intrinsics for those operation that can't be expressed in other ways. So my guess is that if some intrinsics are absolutely needed for some platform, they will probably be there. If an intrinsic is needed, I also don't see a reason why they wouldn't accept a patch that ads it.
Oct 15 2012
On 15 October 2012 17:07, jerro <a a.com> wrote:On Monday, 15 October 2012 at 13:43:28 UTC, Manu wrote:at leastOn 15 October 2012 16:34, jerro <a a.com> wrote: On Monday, 15 October 2012 at 12:19:47 UTC, Manu wrote:On 15 October 2012 02:50, jerro <a a.com> wrote:Speaking of test =E2=80=93 are they available somewhere? Now that LDC=/jerro/phobos/tree/std.simd>theoretically supports most of the GCC builtins, I'd like to throwsome tests at it to see what happens. David I have a fork of std.simd with LDC support athttps://github.com/jerro/** phobos/tree/std.simd <https://github.com/jerro/**** phobos/tree/std.simd <https://github.com/jerro/**phobos/tree/std.simd=<https://**github.com/jerro/phobos/tree/**std.simd<https://github.com=tps://github.com/jerro/std.****simd-tests>and some tests for it at https://github.com/jerro/std.******simd-tests<ht=/std.**simd-tests><https://github.**com/jerro/std.**simd-tests<https://github.com/jerro=rI did change an API for a few functions like loadUnaligned, though. In those cases the signatures needed to be changed because the functions used T or T* for scalar parameters and return types and Vector!T for the vector parameters and return types. This only compiles if T is a static array which I don't think makes much sense. I changed those to take the vecto=<https://github.**com/jerro/**std.simd-tests<https://github.** com/jerro/std.simd-tests <https://github.com/jerro/std.simd-tests>>.Awesome. Pull request plz! :)etype as a template parameter. The vector type can not be inferred from the scalar type because you can use vector registers of different sizes simultaneously (with AVX, for example). Because of that the vector type must be passed explicitly for some functions, so I made it the first template parameter in those cases, so that Ver doesn't always need to b=mespecified. There is one more issue that I need to solve (and that may be a problem in some cases with GDC too) - the pure, safe and nothrow attributes. Currently gcc builtin declarations in LDC have none of those attributes (I have to look into which of those can be added and if it can be done automatically). I've just commented out the attributes in my std.simd fork for now, but this isn't a proper solution. That said, how did you come up with a lot of these implementations? So=s.don't look particularly efficient, and others don't even look right. xor for instance: return cast(T) (cast(int4) v1 ^ cast(int4) v2); This is wrong for float types. x86 has separate instructions for doing this to floats, which make sure to do the right thing by the flags register=s.Most of the LDC blocks assume that it could be any architecture... I don't think this will produce good portable code. It needs to be much more cafully hand-crafted, but it's a nice working start.The problem is that LLVM doesn't provide intrinsics for those operation=yThe xor function does compile to a single xorps instruction when compiling with -O1 or higher, though. I have looked at the code generated for man=d(most, I think, but not for all possible types) of those LDC blocks and most of them compile to the appropriate single instruction when compile=lywith -O2 or -O3. Even the ones for which the D source code looks horrib=tinefficient like for example loadUnaligned. By the way, clang does those in a similar way. For example, here is wha=eclang emits for a wrapper around _mm_xor_ps when compiled with -O1 -emit-llvm: define <4 x float> foo(<4 x float> %a, <4 x float> %b) nounwind uwtabl=lyreadnone { %1 =3D bitcast <4 x float> %a to <4 x i32> %2 =3D bitcast <4 x float> %b to <4 x i32> %3 =3D xor <4 x i32> %1, %2 %4 =3D bitcast <4 x i32> %3 to <4 x float> ret <4 x float> %4 } AFAICT, the only way to ensure that a certain instruction will be used with LDC when there is no LLVM intrinsic for it is to use inline assemb=texpressions. I remember having some problems with those in the past, bu=atit could be that I was doing something wrong. Maybe we should look into th=onI don't see a reason why the compiler couldn't reorder code around GCC style inline assembly blocks. You are supposed to specify which registers are changed in the block. Doesn't that give the compiler enough informati=option too.Inline assembly usually ruins optimising (code reordering around inline asm blocks is usually considered impossible).to reorder code?Not necessarily. If you affect various flags registers or whatever, or direct memory access might violate it's assumptions about the state of memory/stack. I don't think I've come in contact with any compiler's that aren't super-conservative about this sort of thing. It's interesting that the x86 codegen makes such good sense of thoseansequences, but I'm rather more concerned about other platforms. I wonder if other platforms have a similarly incomplete subset of intrinsics? :/It looks to me like LLVM does provide intrinsics for those operation that can't be expressed in other ways. So my guess is that if some intrinsics are absolutely needed for some platform, they will probably be there. If =intrinsic is needed, I also don't see a reason why they wouldn't accept a patch that ads it.Fair enough. Interesting to know. This means that cross-platform LDC SIMD code will need to be thoroughly scrutinised for codegen quality in all targets.
Oct 15 2012
On 7 October 2012 13:12, Manu <turkeyman gmail.com> wrote:On 5 October 2012 14:46, Iain Buclaw <ibuclaw ubuntu.com> wrote:I fixed them again. https://github.com/D-Programming-GDC/GDC/commit/9402516e0b07031e841a15849f5dc94ae81dccdc#L4R1201 float a = 1, b = 2, c = 3, d = 4; float4 f = [a,b,c,d]; ===> movss -16(%rbp), %xmm0 movss -12(%rbp), %xmm1 Regards -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';On 5 October 2012 11:28, Manu <turkeyman gmail.com> wrote:Perfect! I can get on with my unittests :POn 3 October 2012 16:40, Iain Buclaw <ibuclaw ubuntu.com> wrote:They get passed to the backend as of 2.060 - so looks like the semantic passes now allow them. I've just recently added backend support in GDC - https://github.com/D-Programming-GDC/GDC/commit/7ada3d95b8af1b271d82f1ec5208f0b689eb143c#L1R1194 The codegen looks like so: float4 a = 2; float4 b = [1,2,3,4]; ==> vector(4) float a = { 2.0e+0, 2.0e+0, 2.0e+0, 2.0e+0 }; vector(4) float b = { 1.0e+0, 2.0e+0, 3.0e+0, 4.0e+0 }; ==> movaps .LC0, %xmm0 movaps %xmm0, -24(%ebp) movaps .LC1, %xmm0 movaps %xmm0, -40(%ebp) .align 16 .LC0: .long 1073741824 .long 1073741824 .long 1073741824 .long 1073741824 .align 16 .LC1: .long 1065353216 .long 1073741824 .long 1077936128 .long 1082130432On 3 October 2012 02:31, jerro <a a.com> wrote:I didn't realise vector literals like that were supported properly in the front end yet? Do they work at all? What does the code generated look like?Then don't just talk about it, raise a bug - otherwise how do you expect it to get fixed! ( http://www.gdcproject.org/bugzilla ) I've made a note of the error you get with `__vector(float[4]) c = [1,2,3,4];' - That is because vector expressions implementation is very basic at the moment. Look forward to hear from all your experiences so we can make vector support rock solid in GDC. ;-)import core.simd, std.stdio; void main() { float4 a = 1, b = 2; writeln((a + b).array); // WORKS: [3, 3, 3, 3] float4 c = [1, 2, 3, 4]; // ERROR: "Stored value type does // not match pointer operand type!" // [..a bunch of LLVM error code..] float4 c = 0, d = 1; c.array[0] = 4; c.ptr[1] = 4; writeln((c + d).array); // WRONG: [1, 1, 1, 1] }Oh, that doesn't work for me either. I never tried to use those, so I didn't notice that before. This code gives me internal compiler errors with GDC and DMD too (with "float4 c = [1, 2, 3, 4]" commented out). I'm using DMD 2.060 and a recent versions of GDC and LDC on 64 bit Linux.
Oct 08 2012
Iain Buclaw wrote:I fixed them again. https://github.com/D-Programming-GDC/GDC/commit/9402516e0b07031e841a15849f5dc94ae81dccdc#L4R1201 float a = 1, b = 2, c = 3, d = 4; float4 f = [a,b,c,d]; ===> movss -16(%rbp), %xmm0 movss -12(%rbp), %xmm1Nice, not even DMD can do this yet. Can these changes be pushed upstream? On a side note, I understand GDC doesn't support the core.simd.__simd(...) command, and I'm sure you have good reasons for this. However, it would still be nice if: a) this interface was supported through function-wrappers, or.. b) DMD/LDC could find common ground with GDC in SIMD instructions I just think this sort of difference should be worked out early on. If this simply can't or won't be changed, would you mind giving a short explanation as to why? (Please forgive if you've explained this already before). Is core.simd designed to really never be used and Manu's std.simd is really the starting place for libraries? (I believe I remember him mentioning that)
Oct 08 2012
On 8 October 2012 22:18, F i L <witte2008 gmail.com> wrote:Iain Buclaw wrote:I'm refusing to implement any intrinsic that is tied to a specific architecture. -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';I fixed them again. https://github.com/D-Programming-GDC/GDC/commit/9402516e0b07031e841a15849f5dc94ae81dccdc#L4R1201 float a = 1, b = 2, c = 3, d = 4; float4 f = [a,b,c,d]; ===> movss -16(%rbp), %xmm0 movss -12(%rbp), %xmm1Nice, not even DMD can do this yet. Can these changes be pushed upstream? On a side note, I understand GDC doesn't support the core.simd.__simd(...) command, and I'm sure you have good reasons for this. However, it would still be nice if: a) this interface was supported through function-wrappers, or.. b) DMD/LDC could find common ground with GDC in SIMD instructions I just think this sort of difference should be worked out early on. If this simply can't or won't be changed, would you mind giving a short explanation as to why? (Please forgive if you've explained this already before). Is core.simd designed to really never be used and Manu's std.simd is really the starting place for libraries? (I believe I remember him mentioning that)
Oct 08 2012
Iain Buclaw wrote:I'm refusing to implement any intrinsic that is tied to a specific architecture.I see. So the __builtin_ia32_***() functions in gcc.builtins are architecture agnostic? I couldn't find much documentation about them on the web. Do you have any references you could pass on? I guess it makes sense to just make std.simd the lib everyone uses for a "base-line" support of SIMD and let DMD do what it wants with it's core.simd lib. It sounds like gcc.builtins is just a layer above core.simd anyways. Although now it seems that DMD's std.simd will need a bunch of 'static if (architectureX) { ... }' for every GDC builtin... wounder if later that shouldn't be moved to (and standerized) a 'core.builtins' module or something. Thanks for the explanation.
Oct 08 2012
On 9 October 2012 00:38, F i L <witte2008 gmail.com> wrote:Iain Buclaw wrote:gcc.builtins does something different depending on architecure, and target cpu flags. All I do is take what gcc backend gives to the frontend, and hash it out to D. What I meant is that I won't implement a frontend intrinsic that... -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';I'm refusing to implement any intrinsic that is tied to a specific architecture.I see. So the __builtin_ia32_***() functions in gcc.builtins are architecture agnostic? I couldn't find much documentation about them on the web. Do you have any references you could pass on? I guess it makes sense to just make std.simd the lib everyone uses for a "base-line" support of SIMD and let DMD do what it wants with it's core.simd lib. It sounds like gcc.builtins is just a layer above core.simd anyways. Although now it seems that DMD's std.simd will need a bunch of 'static if (architectureX) { ... }' for every GDC builtin... wounder if later that shouldn't be moved to (and standerized) a 'core.builtins' module or something. Thanks for the explanation.
Oct 08 2012
On 9 October 2012 02:38, F i L <witte2008 gmail.com> wrote:Iain Buclaw wrote:std.simd already does have a mammoth mess of static if(arch & compiler). The thing about std.simd is that it's designed to be portable, so it doesn't make sense to expose the low-level sse intrinsics directly there. But giving it some thought, it might be nice to produce std.simd.sse and std.simd.vmx, etc for collating the intrinsics used by different compilers, and anyone who is writing sse code explicitly might use std.simd.sse to avoid having to support all different compilers intrinsics themselves. This sounds like a reasonable approach, the only question is what all these wrappers will do to the code-gen. I'll need to experiment/prove that out.I'm refusing to implement any intrinsic that is tied to a specific architecture.I see. So the __builtin_ia32_***() functions in gcc.builtins are architecture agnostic? I couldn't find much documentation about them on the web. Do you have any references you could pass on? I guess it makes sense to just make std.simd the lib everyone uses for a "base-line" support of SIMD and let DMD do what it wants with it's core.simd lib. It sounds like gcc.builtins is just a layer above core.simd anyways. Although now it seems that DMD's std.simd will need a bunch of 'static if (architectureX) { ... }' for every GDC builtin... wounder if later that shouldn't be moved to (and standerized) a 'core.builtins' module or something. Thanks for the explanation.
Oct 09 2012
On 2012-10-09 09:50, Manu wrote:std.simd already does have a mammoth mess of static if(arch & compiler). The thing about std.simd is that it's designed to be portable, so it doesn't make sense to expose the low-level sse intrinsics directly there. But giving it some thought, it might be nice to produce std.simd.sse and std.simd.vmx, etc for collating the intrinsics used by different compilers, and anyone who is writing sse code explicitly might use std.simd.sse to avoid having to support all different compilers intrinsics themselves. This sounds like a reasonable approach, the only question is what all these wrappers will do to the code-gen. I'll need to experiment/prove that out.An alternative approach is to have one module per architecture or compiler. -- /Jacob Carlborg
Oct 09 2012
An alternative approach is to have one module per architecture or compiler.You mean like something like std.simd.x86_gdc? In this case a user would need to write a different version of his code for each compiler or write his own wrappers (which is basically what we have now). This could cause a lot of redundant work. What is worse, some people wouldn't bother, and then we would have code that only works with one D compiler.
Oct 09 2012
On 2012-10-09, 16:20, jerro wrote:Nope, like: module std.simd; version(Linux64) { public import std.internal.simd_linux64; } Then all std.internal.simd_* modules have the same public interface, and only the version that fits /your/ platform will be included. -- SimenAn alternative approach is to have one module per architecture or compiler.You mean like something like std.simd.x86_gdc? In this case a user would need to write a different version of his code for each compiler or write his own wrappers (which is basically what we have now). This could cause a lot of redundant work. What is worse, some people wouldn't bother, and then we would have code that only works with one D compiler.
Oct 09 2012
On 2012-10-09 16:52, Simen Kjaeraas wrote:Nope, like: module std.simd; version(Linux64) { public import std.internal.simd_linux64; } Then all std.internal.simd_* modules have the same public interface, and only the version that fits /your/ platform will be included.Exactly, what he said. -- /Jacob Carlborg
Oct 09 2012
On Tuesday, 9 October 2012 at 16:59:58 UTC, Jacob Carlborg wrote:On 2012-10-09 16:52, Simen Kjaeraas wrote:I'm guessing the platform in this case would be the CPU architecture, since that determines what SIMD instructions are available, not the OS. But anyway, this does not address the problem Manu was talking about. The problem is that the API for the intrisics for the same architecture is not consistent across compilers. So for example, if you wanted to generate the instruction "movaps XMM1, XMM2, 0x88" (this extracts all even elements from two vectors), you would need to write: version(GNU) { return __builtin_ia32_shufps(a, b, 0x88); } else version(LDC) { return shufflevector(a, b, 0, 2, 4, 6); } else version(DMD) { // can't do that in DMD yet, but the way to do it will probably be different from the way it is done in LDC and GDC } What Manu meant with having std.simd.sse and std.simd.neon was to have modules that would provide access to the platform dependent instructions that would be portable across compilers. So for the shufps instruction above you would have something like this ins std.simd.sse: float4 shufps(int i0, int i1, int i2, int i3)(float4 a, float4 b){ ... } std.simd currently takes care of cases when the code can be written in a cross platform way. But when you need to use platform specific instructions directly, std.simd doesn't currently help you, while std.simd.sse, std.simd.neon and others would. What Manu is worried about is that having instructions wrapped in another level of functions would hurt performance. It certainly would slow things down in debug builds (and IIRC he has written in his previous posts that he does care about that). I don't think it would make much of a difference when compiled with optimizations turned on, at least not with LDC and GDC.Nope, like: module std.simd; version(Linux64) { public import std.internal.simd_linux64; } Then all std.internal.simd_* modules have the same public interface, and only the version that fits /your/ platform will be included.Exactly, what he said.
Oct 09 2012
On 9 October 2012 20:46, jerro <a a.com> wrote:On Tuesday, 9 October 2012 at 16:59:58 UTC, Jacob Carlborg wrote:Perfect! You saved me writing anything at all ;) I do indeed care about debug builds, but one interesting possibility that I discussed with Walter last week was a #pragma inline statement, which may force-enable inlining even in debug. I'm not sure how that would translate to GDC/LDC, and that's an important consideration. I'd also like to prove that the code-gen does work well with 2 or 3 levels of inlining, and that the optimiser is still able to perform sensible code reordering in the target context.On 2012-10-09 16:52, Simen Kjaeraas wrote: Nope, like:I'm guessing the platform in this case would be the CPU architecture, since that determines what SIMD instructions are available, not the OS. But anyway, this does not address the problem Manu was talking about. The problem is that the API for the intrisics for the same architecture is not consistent across compilers. So for example, if you wanted to generate the instruction "movaps XMM1, XMM2, 0x88" (this extracts all even elements from two vectors), you would need to write: version(GNU) { return __builtin_ia32_shufps(a, b, 0x88); } else version(LDC) { return shufflevector(a, b, 0, 2, 4, 6); } else version(DMD) { // can't do that in DMD yet, but the way to do it will probably be different from the way it is done in LDC and GDC } What Manu meant with having std.simd.sse and std.simd.neon was to have modules that would provide access to the platform dependent instructions that would be portable across compilers. So for the shufps instruction above you would have something like this ins std.simd.sse: float4 shufps(int i0, int i1, int i2, int i3)(float4 a, float4 b){ ... } std.simd currently takes care of cases when the code can be written in a cross platform way. But when you need to use platform specific instructions directly, std.simd doesn't currently help you, while std.simd.sse, std.simd.neon and others would. What Manu is worried about is that having instructions wrapped in another level of functions would hurt performance. It certainly would slow things down in debug builds (and IIRC he has written in his previous posts that he does care about that). I don't think it would make much of a difference when compiled with optimizations turned on, at least not with LDC and GDC.module std.simd; version(Linux64) { public import std.internal.simd_linux64; } Then all std.internal.simd_* modules have the same public interface, and only the version that fits /your/ platform will be included.Exactly, what he said.
Oct 10 2012
On Wednesday, 10 October 2012 at 08:33:39 UTC, Manu wrote:I do indeed care about debug builds, but one interesting possibility that I discussed with Walter last week was a #pragma inline statement, which may force-enable inlining even in debug. I'm not sure how that would translate to GDC/LDC, […]pragma(always_inline) or something like that would be trivially easy to implement in LDC. David
Oct 10 2012
On 10 October 2012 12:25, David Nadlinger <see klickverbot.at> wrote:On Wednesday, 10 October 2012 at 08:33:39 UTC, Manu wrote:tI do indeed care about debug builds, but one interesting possibility tha=yI discussed with Walter last week was a #pragma inline statement, which ma=teforce-enable inlining even in debug. I'm not sure how that would transla=Currently pragma(attribute, alway_inline) in GDC, but I am considering scrapping pragma(attribute) - the current implementation kept only for attributes used by gcc builtin functions - and introduce each supported attribute as an individual pragma. --=20 Iain Buclaw *(p < e ? p++ : p) =3D (c & 0x0f) + '0';to GDC/LDC, [=85]pragma(always_inline) or something like that would be trivially easy to implement in LDC. David
Oct 10 2012
On 10 October 2012 14:50, Iain Buclaw <ibuclaw ubuntu.com> wrote:On 10 October 2012 12:25, David Nadlinger <see klickverbot.at> wrote:Right, well that's encouraging then. Maybe all the pieces fit, and we can perform liberal wrapping of the compiler-specific intrinsics in that case.On Wednesday, 10 October 2012 at 08:33:39 UTC, Manu wrote:thatI do indeed care about debug builds, but one interesting possibilitymayI discussed with Walter last week was a #pragma inline statement, whichtranslateforce-enable inlining even in debug. I'm not sure how that wouldCurrently pragma(attribute, alway_inline) in GDC, but I am considering scrapping pragma(attribute) - the current implementation kept only for attributes used by gcc builtin functions - and introduce each supported attribute as an individual pragma.to GDC/LDC, [=E2=80=A6]pragma(always_inline) or something like that would be trivially easy to implement in LDC. David
Oct 10 2012
Manu wrote:std.simd already does have a mammoth mess of static if(arch & compiler). The thing about std.simd is that it's designed to be portable, so it doesn't make sense to expose the low-level sse intrinsics directly there.Well, that's not really what I was suggesting. I was saying maybe eventually matching the agnostic gdc builtins in a separate module: // core.builtins import core.simd; version (GNU) import gcc.builtins; void madd(ref float4 r, float4 a, float4 b) { version (X86_OR_X64) { version (DigitalMars) { r = __simd(XMM.PMADDWD, a, b); } else version (GNU) { __builtin_ia32_fmaddpd(r, a, b) } } } then std.simd can just use a single function (madd) and forget about all the compiler-specific switches. This may be more work than it's worth and std.simd should just contain all the platform specific switches... idk, i'm just throwing out ideas.
Oct 09 2012
On Tuesday, 9 October 2012 at 19:18:35 UTC, F i L wrote:Manu wrote:You know... now that I think about it, this is pretty much EXACTLY what std.simd IS already... lol, forget all of that, please.std.simd already does have a mammoth mess of static if(arch & compiler). The thing about std.simd is that it's designed to be portable, so it doesn't make sense to expose the low-level sse intrinsics directly there.Well, that's not really what I was suggesting. I was saying maybe eventually matching the agnostic gdc builtins in a separate module: // core.builtins import core.simd; version (GNU) import gcc.builtins; void madd(ref float4 r, float4 a, float4 b) { version (X86_OR_X64) { version (DigitalMars) { r = __simd(XMM.PMADDWD, a, b); } else version (GNU) { __builtin_ia32_fmaddpd(r, a, b) } } } then std.simd can just use a single function (madd) and forget about all the compiler-specific switches. This may be more work than it's worth and std.simd should just contain all the platform specific switches... idk, i'm just throwing out ideas.
Oct 09 2012
On 9 October 2012 21:56, F i L <witte2008 gmail.com> wrote:On Tuesday, 9 October 2012 at 19:18:35 UTC, F i L wrote:Yes, I was gonna say... We're discussing providing convenient access to the arch intrinsics directly, which may be useful in many situations, although I think use of std.simd would be encouraged for the most part, for portability reasons. I'll take some time this weekend to do some experiments with GDC and LDC... actually, no I won't, I'm doing a 48 hour game jam (which I'll probably write in D too), but I'll do it soon! ;)Manu wrote:You know... now that I think about it, this is pretty much EXACTLY what std.simd IS already... lol, forget all of that, please.std.simd already does have a mammoth mess of static if(arch & compiler). The thing about std.simd is that it's designed to be portable, so it doesn't make sense to expose the low-level sse intrinsics directly there.Well, that's not really what I was suggesting. I was saying maybe eventually matching the agnostic gdc builtins in a separate module: // core.builtins import core.simd; version (GNU) import gcc.builtins; void madd(ref float4 r, float4 a, float4 b) { version (X86_OR_X64) { version (DigitalMars) { r = __simd(XMM.PMADDWD, a, b); } else version (GNU) { __builtin_ia32_fmaddpd(r, a, b) } } } then std.simd can just use a single function (madd) and forget about all the compiler-specific switches. This may be more work than it's worth and std.simd should just contain all the platform specific switches... idk, i'm just throwing out ideas.
Oct 10 2012
Manu wrote:actually, no I won't, I'm doing a 48 hour game jam (which I'll probably write in D too), but I'll do it soon! ;)Nice :) For a competition or casual? I would love to see what you come up with. My brother and I released our second game (this time written with our own game-engine) awhile back: http://www.youtube.com/watch?v=7pvCcgQiXNk Right now we're working on building 3D animation and Physics into the engine for that once it's to a certain point I'll be porting it to D.
Oct 10 2012
On 10 October 2012 17:53, F i L <witte2008 gmail.com> wrote:Manu wrote:It's a work event. Weekend office party effectively, with lots of beer and sauna (essential ingredients in any Finnish happenings!) I expect it'll be open source, should be on github, whatever it is. I'll build it on my toy engine (also open source): https://github.com/TurkeyMan/fuji/wiki, which has D bindings.actually, no I won't, I'm doing a 48 hour game jam (which I'll probably write in D too), but I'll do it soon! ;)Nice :) For a competition or casual? I would love to see what you come up with. My brother and I released our second game (this time written with our own game-engine) awhile back: http://www.youtube.com/watch?**v=7pvCcgQiXNk<http://www.youtube.com/watch v=7pvCcgQiXNk>Right now we're working on building 3D animation and Physics into the awhile that once it's to a certain point I'll be porting it to D.
Oct 10 2012
On 9 October 2012 00:18, F i L <witte2008 gmail.com> wrote:Iain Buclaw wrote:core.simd just provides what the compiler provides in it's most primal state. As far as I'm concerned, it's just not meant to be used directly except by library authors. It's possible that a uniform suite of names could be made to wrap all the compiler-specific names (ldc is different again), but that would just get wrapped a second time one level higher. Hardly seems worth the effort.I fixed them again. https://github.com/D-**Programming-GDC/GDC/commit/** 9402516e0b07031e841a15849f5dc9**4ae81dccdc#L4R1201<https://github.com/D-Programming-GDC/GDC/commit/9402516e0b07031e841a15849f5dc94ae81dccdc#L4R1201> float a = 1, b = 2, c = 3, d = 4; float4 f = [a,b,c,d]; ===> movss -16(%rbp), %xmm0 movss -12(%rbp), %xmm1Nice, not even DMD can do this yet. Can these changes be pushed upstream? On a side note, I understand GDC doesn't support the core.simd.__simd(...) command, and I'm sure you have good reasons for this. However, it would still be nice if: a) this interface was supported through function-wrappers, or.. b) DMD/LDC could find common ground with GDC in SIMD instructions I just think this sort of difference should be worked out early on. If this simply can't or won't be changed, would you mind giving a short explanation as to why? (Please forgive if you've explained this already before). Is core.simd designed to really never be used and Manu's std.simd is really the starting place for libraries? (I believe I remember him mentioning that)
Oct 08 2012
On 9 October 2012 00:30, Iain Buclaw <ibuclaw ubuntu.com> wrote:On 8 October 2012 22:18, F i L <witte2008 gmail.com> wrote:GCC offers perfectly good intrinsics anyway. And they're superior to the DMD intrinsics too.Iain Buclaw wrote:https://github.com/D-Programming-GDC/GDC/commit/9402516e0b07031e841a15849f5dc94ae81dccdc#L4R1201I fixed them again.core.simd.__simd(...)float a = 1, b = 2, c = 3, d = 4; float4 f = [a,b,c,d]; ===> movss -16(%rbp), %xmm0 movss -12(%rbp), %xmm1Nice, not even DMD can do this yet. Can these changes be pushed upstream? On a side note, I understand GDC doesn't support thecommand, and I'm sure you have good reasons for this. However, it would still be nice if: a) this interface was supported through function-wrappers, or.. b) DMD/LDC could find common ground with GDC in SIMD instructions I just think this sort of difference should be worked out early on. Ifthissimply can't or won't be changed, would you mind giving a shortexplanationas to why? (Please forgive if you've explained this already before). Is core.simd designed to really never be used and Manu's std.simd is reallythestarting place for libraries? (I believe I remember him mentioning that)I'm refusing to implement any intrinsic that is tied to a specific architecture.
Oct 08 2012
On 8 October 2012 22:34, Manu <turkeyman gmail.com> wrote:On 9 October 2012 00:30, Iain Buclaw <ibuclaw ubuntu.com> wrote:Provided that a) the architecture provides them, and b) you have the right -march/-mtune flags turned on. -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';On 8 October 2012 22:18, F i L <witte2008 gmail.com> wrote:GCC offers perfectly good intrinsics anyway. And they're superior to the DMD intrinsics too.Iain Buclaw wrote:I'm refusing to implement any intrinsic that is tied to a specific architecture.I fixed them again. https://github.com/D-Programming-GDC/GDC/commit/9402516e0b07031e841a15849f5dc94ae81dccdc#L4R1201 float a = 1, b = 2, c = 3, d = 4; float4 f = [a,b,c,d]; ===> movss -16(%rbp), %xmm0 movss -12(%rbp), %xmm1Nice, not even DMD can do this yet. Can these changes be pushed upstream? On a side note, I understand GDC doesn't support the core.simd.__simd(...) command, and I'm sure you have good reasons for this. However, it would still be nice if: a) this interface was supported through function-wrappers, or.. b) DMD/LDC could find common ground with GDC in SIMD instructions I just think this sort of difference should be worked out early on. If this simply can't or won't be changed, would you mind giving a short explanation as to why? (Please forgive if you've explained this already before). Is core.simd designed to really never be used and Manu's std.simd is really the starting place for libraries? (I believe I remember him mentioning that)
Oct 08 2012
On Monday, 8 October 2012 at 21:36:08 UTC, F i L wrote:Iain Buclaw wrote:No, the actual codegen is compilers-specific (and apparently wrong in the case of GDC, if this is the actual piece of code emitted for the code snippet).float a = 1, b = 2, c = 3, d = 4; float4 f = [a,b,c,d]; ===> movss -16(%rbp), %xmm0 movss -12(%rbp), %xmm1Nice, not even DMD can do this yet. Can these changes be pushed upstream?On a side note, I understand GDC doesn't support the core.simd.__simd(...) command, and I'm sure you have good reasons for this. However, it would still be nice if: a) this interface was supported through function-wrappers, or.. b) DMD/LDC could find common ground with GDC in SIMD instructionsLDC won't support core.simd.__simd in the forseeable future either. The reason is that it is a) untyped and b) highly x86-specific, both of which make it hard to integrate with LLVM – __simd is really just a glorified inline assembly expression (hm, this makes me think, maybe it could be implemented quite easily in terms of a transformation to LLVM inline assembly expressions…).Is core.simd designed to really never be used and Manu's std.simd is really the starting place for libraries? (I believe I remember him mentioning that)With all due respect to Walter, core.simd isn't really "designed" much at all, or at least this isn't visible in its current state – it rather seems like a quick hack to get some basic SIMD code working with DMD (but beware of ICEs). Walter, if you are following this thread, do you have any plans for SIMD on non-x86 platforms? David
Oct 08 2012
On 9 October 2012 02:52, David Nadlinger <see klickverbot.at> wrote:Is core.simd designed to really never be used and Manu's std.simd isher seemsreally the starting place for libraries? (I believe I remember him mentioning that)With all due respect to Walter, core.simd isn't really "designed" much at all, or at least this isn't visible in its current state =E2=80=93 it rat=like a quick hack to get some basic SIMD code working with DMD (but bewar=eof ICEs). Walter, if you are following this thread, do you have any plans for SIMD on non-x86 platforms?DMD doesn't support non-x86 platforms... What DMD offer's is fine, since it all needs to be collated anyway; GDC/LDC don't agree on intrinsics either. I already support ARM and did some PPC experiments in std.simd. I just use the intrinsics that gdc/ldc provide, that's perfectly fine. As said in my prior post, I think std.simd.sse, std.simd.neon, and friends, might all be a valuable addition. But we'll need to see about the codegen after it unravels a bunch of wrappers...
Oct 09 2012
On Tuesday, 9 October 2012 at 08:13:39 UTC, Manu wrote:DMD doesn't support non-x86 platforms... What DMD offer's is fine, since it all needs to be collated anyway; GDC/LDC don't agree on intrinsics either.By the way, I just committed a patch to auto-generate GCC->LLVM intrinsic mappings to LDC – thanks, Jernej! –, which would mean that you could in theory use the GDC code path for LDC as well. David
Oct 14 2012
David Nadlinger wrote:By the way, I just committed a patch to auto-generate GCC->LLVM intrinsic mappings to LDC – thanks, Jernej! –, which would mean that you could in theory use the GDC code path for LDC as well.Your awesome, David!
Oct 14 2012
On Sunday, 14 October 2012 at 19:40:08 UTC, F i L wrote:David Nadlinger wrote:Usually, yes, but in this case even I must admit that it was jerro who did the work… :P DavidBy the way, I just committed a patch to auto-generate GCC->LLVM intrinsic mappings to LDC – thanks, Jernej! –, which would mean that you could in theory use the GDC code path for LDC as well.Your awesome, David!
Oct 14 2012
On 9 October 2012 00:52, David Nadlinger <see klickverbot.at> wrote:On Monday, 8 October 2012 at 21:36:08 UTC, F i L wrote:?Iain Buclaw wrote:float a =3D 1, b =3D 2, c =3D 3, d =3D 4; float4 f =3D [a,b,c,d]; =3D=3D=3D> movss -16(%rbp), %xmm0 movss -12(%rbp), %xmm1Nice, not even DMD can do this yet. Can these changes be pushed upstream=No, the actual codegen is compilers-specific (and apparently wrong in the case of GDC, if this is the actual piece of code emitted for the code snippet)..)On a side note, I understand GDC doesn't support the core.simd.__simd(..=emscommand, and I'm sure you have good reasons for this. However, it would still be nice if: a) this interface was supported through function-wrappers, or.. b) DMD/LDC could find common ground with GDC in SIMD instructionsLDC won't support core.simd.__simd in the forseeable future either. The reason is that it is a) untyped and b) highly x86-specific, both of which make it hard to integrate with LLVM =96 __simd is really just a glorified inline assembly expression (hm, this makes me think, maybe it could be implemented quite easily in terms of a transformation to LLVM inline assembly expressions=85).Is core.simd designed to really never be used and Manu's std.simd is really the starting place for libraries? (I believe I remember him mentioning that)With all due respect to Walter, core.simd isn't really "designed" much at all, or at least this isn't visible in its current state =96 it rather se=like a quick hack to get some basic SIMD code working with DMD (but bewar=eof ICEs). Walter, if you are following this thread, do you have any plans for SIMD =onnon-x86 platforms? DavidVector types already support the same basic operations that can be done on D arrays. So that itself guarantees cross platform. --=20 Iain Buclaw *(p < e ? p++ : p) =3D (c & 0x0f) + '0';
Oct 09 2012
On Tuesday, 9 October 2012 at 10:29:25 UTC, Iain Buclaw wrote:Vector types already support the same basic operations that can be done on D arrays. So that itself guarantees cross platform.That's obviously true, but not at all enough for most of the "interesting" use cases of vector types (otherwise, you could use array operations just as well). You need at least some sort of broadcasting/swizzling support for it to be interesting. David
Oct 09 2012
On Tuesday, 9 October 2012 at 10:29:25 UTC, Iain Buclaw wrote:Vector types already support the same basic operations that can be done on D arrays. So that itself guarantees cross platform.That's obviously true, but not at all enough for most of the "interesting" use cases of vector types (otherwise, you could use array operations just as well). You need at least some sort of broadcasting/swizzling support for it to be interesting. David
Oct 09 2012
On 10/8/2012 4:52 PM, David Nadlinger wrote:With all due respect to Walter, core.simd isn't really "designed" much at all, or at least this isn't visible in its current state – it rather seems like a quick hack to get some basic SIMD code working with DMD (but beware of ICEs).That is correct. I have little experience with SIMD on x86, and none on other platforms. I'm not in a good position to do a portable and useful design. I was mainly interested in providing a very low level method for a more useful design that could be layered over it.Walter, if you are following this thread, do you have any plans for SIMD on non-x86 platforms?I'm going to leave that up to those who are doing non-x86 platforms for now.
Oct 14 2012
On Monday, 8 October 2012 at 20:23:50 UTC, Iain Buclaw wrote:float a = 1, b = 2, c = 3, d = 4; float4 f = [a,b,c,d]; ===> movss -16(%rbp), %xmm0 movss -12(%rbp), %xmm1The obligatory "me too" post: LDC turns --- import core.simd; struct T { float a, b, c, d; ubyte[100] passOnStack; } void test(T t) { receiver([t.a, t.b, t.c, t.d]); } void receiver(float4 f); --- into --- 0000000000000000 <_D4test4testFS4test1TZv>: 0: 50 push rax 1: 0f 28 44 24 10 movaps xmm0,XMMWORD PTR [rsp+0x10] 6: e8 00 00 00 00 call b <_D4test4testFS4test1TZv+0xb> b: 58 pop rax c: c3 ret --- (the struct is just there so that the values are actually on the stack, and receiver just so that the optimizer doesn't eat everything for breakfast). David
Oct 08 2012
On 8 October 2012 23:05, Iain Buclaw <ibuclaw ubuntu.com> wrote:On 7 October 2012 13:12, Manu <turkeyman gmail.com> wrote:Errr, that's not fixed...? movss is not the opcode you're looking for. Surely that should produce a single movaps...On 5 October 2012 14:46, Iain Buclaw <ibuclaw ubuntu.com> wrote:so IOn 5 October 2012 11:28, Manu <turkeyman gmail.com> wrote:On 3 October 2012 16:40, Iain Buclaw <ibuclaw ubuntu.com> wrote:On 3 October 2012 02:31, jerro <a a.com> wrote:import core.simd, std.stdio; void main() { float4 a = 1, b = 2; writeln((a + b).array); // WORKS: [3, 3, 3, 3] float4 c = [1, 2, 3, 4]; // ERROR: "Stored value type does // not match pointer operand type!" // [..a bunch of LLVM error code..] float4 c = 0, d = 1; c.array[0] = 4; c.ptr[1] = 4; writeln((c + d).array); // WRONG: [1, 1, 1, 1] }Oh, that doesn't work for me either. I never tried to use those,withdidn't notice that before. This code gives me internal compiler errorsDMDGDC and DMD too (with "float4 c = [1, 2, 3, 4]" commented out). I'm usinghttps://github.com/D-Programming-GDC/GDC/commit/7ada3d95b8af1b271d82f1ec5208f0b689eb143c#L1R1194They get passed to the backend as of 2.060 - so looks like the semantic passes now allow them. I've just recently added backend support in GDC -I didn't realise vector literals like that were supported properly in the front end yet? Do they work at all? What does the code generated look like?2.060 and a recent versions of GDC and LDC on 64 bit Linux.Then don't just talk about it, raise a bug - otherwise how do you expect it to get fixed! ( http://www.gdcproject.org/bugzilla ) I've made a note of the error you get with `__vector(float[4]) c = [1,2,3,4];' - That is because vector expressions implementation is very basic at the moment. Look forward to hear from all your experiences so we can make vector support rock solid in GDC. ;-)I fixed them again. https://github.com/D-Programming-GDC/GDC/commit/9402516e0b07031e841a15849f5dc94ae81dccdc#L4R1201 float a = 1, b = 2, c = 3, d = 4; float4 f = [a,b,c,d]; ===> movss -16(%rbp), %xmm0 movss -12(%rbp), %xmm1The codegen looks like so: float4 a = 2; float4 b = [1,2,3,4]; ==> vector(4) float a = { 2.0e+0, 2.0e+0, 2.0e+0, 2.0e+0 }; vector(4) float b = { 1.0e+0, 2.0e+0, 3.0e+0, 4.0e+0 }; ==> movaps .LC0, %xmm0 movaps %xmm0, -24(%ebp) movaps .LC1, %xmm0 movaps %xmm0, -40(%ebp) .align 16 .LC0: .long 1073741824 .long 1073741824 .long 1073741824 .long 1073741824 .align 16 .LC1: .long 1065353216 .long 1073741824 .long 1077936128 .long 1082130432Perfect! I can get on with my unittests :P
Oct 08 2012
On 8 October 2012 22:18, Manu <turkeyman gmail.com> wrote:On 8 October 2012 23:05, Iain Buclaw <ibuclaw ubuntu.com> wrote:I didn't say I compiled with optimisations - only -march=native. =) -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';On 7 October 2012 13:12, Manu <turkeyman gmail.com> wrote:Errr, that's not fixed...? movss is not the opcode you're looking for. Surely that should produce a single movaps...On 5 October 2012 14:46, Iain Buclaw <ibuclaw ubuntu.com> wrote:I fixed them again. https://github.com/D-Programming-GDC/GDC/commit/9402516e0b07031e841a15849f5dc94ae81dccdc#L4R1201 float a = 1, b = 2, c = 3, d = 4; float4 f = [a,b,c,d]; ===> movss -16(%rbp), %xmm0 movss -12(%rbp), %xmm1On 5 October 2012 11:28, Manu <turkeyman gmail.com> wrote:Perfect! I can get on with my unittests :POn 3 October 2012 16:40, Iain Buclaw <ibuclaw ubuntu.com> wrote:They get passed to the backend as of 2.060 - so looks like the semantic passes now allow them. I've just recently added backend support in GDC - https://github.com/D-Programming-GDC/GDC/commit/7ada3d95b8af1b271d82f1ec5208f0b689eb143c#L1R1194 The codegen looks like so: float4 a = 2; float4 b = [1,2,3,4]; ==> vector(4) float a = { 2.0e+0, 2.0e+0, 2.0e+0, 2.0e+0 }; vector(4) float b = { 1.0e+0, 2.0e+0, 3.0e+0, 4.0e+0 }; ==> movaps .LC0, %xmm0 movaps %xmm0, -24(%ebp) movaps .LC1, %xmm0 movaps %xmm0, -40(%ebp) .align 16 .LC0: .long 1073741824 .long 1073741824 .long 1073741824 .long 1073741824 .align 16 .LC1: .long 1065353216 .long 1073741824 .long 1077936128 .long 1082130432On 3 October 2012 02:31, jerro <a a.com> wrote:I didn't realise vector literals like that were supported properly in the front end yet? Do they work at all? What does the code generated look like?Then don't just talk about it, raise a bug - otherwise how do you expect it to get fixed! ( http://www.gdcproject.org/bugzilla ) I've made a note of the error you get with `__vector(float[4]) c = [1,2,3,4];' - That is because vector expressions implementation is very basic at the moment. Look forward to hear from all your experiences so we can make vector support rock solid in GDC. ;-)import core.simd, std.stdio; void main() { float4 a = 1, b = 2; writeln((a + b).array); // WORKS: [3, 3, 3, 3] float4 c = [1, 2, 3, 4]; // ERROR: "Stored value type does // not match pointer operand type!" // [..a bunch of LLVM error code..] float4 c = 0, d = 1; c.array[0] = 4; c.ptr[1] = 4; writeln((c + d).array); // WRONG: [1, 1, 1, 1] }Oh, that doesn't work for me either. I never tried to use those, so I didn't notice that before. This code gives me internal compiler errors with GDC and DMD too (with "float4 c = [1, 2, 3, 4]" commented out). I'm using DMD 2.060 and a recent versions of GDC and LDC on 64 bit Linux.
Oct 08 2012
On 9 October 2012 00:29, Iain Buclaw <ibuclaw ubuntu.com> wrote:On 8 October 2012 22:18, Manu <turkeyman gmail.com> wrote:Either way, that code is wrong. The prior code was correct (albeit with the redundant store, which I presume would have gone away with optimisation enabled)On 8 October 2012 23:05, Iain Buclaw <ibuclaw ubuntu.com> wrote:usingOn 7 October 2012 13:12, Manu <turkeyman gmail.com> wrote:On 5 October 2012 14:46, Iain Buclaw <ibuclaw ubuntu.com> wrote:On 5 October 2012 11:28, Manu <turkeyman gmail.com> wrote:On 3 October 2012 16:40, Iain Buclaw <ibuclaw ubuntu.com> wrote:On 3 October 2012 02:31, jerro <a a.com> wrote:import core.simd, std.stdio; void main() { float4 a = 1, b = 2; writeln((a + b).array); // WORKS: [3, 3, 3, 3] float4 c = [1, 2, 3, 4]; // ERROR: "Stored value type does // not match pointer operand type!" // [..a bunch of LLVM error code..] float4 c = 0, d = 1; c.array[0] = 4; c.ptr[1] = 4; writeln((c + d).array); // WRONG: [1, 1, 1, 1] }Oh, that doesn't work for me either. I never tried to use those, so I didn't notice that before. This code gives me internal compiler errors with GDC and DMD too (with "float4 c = [1, 2, 3, 4]" commented out). I'minI didn't realise vector literals like that were supported properlyDMD 2.060 and a recent versions of GDC and LDC on 64 bit Linux.Then don't just talk about it, raise a bug - otherwise how do you expect it to get fixed! ( http://www.gdcproject.org/bugzilla ) I've made a note of the error you get with `__vector(float[4]) c = [1,2,3,4];' - That is because vector expressions implementation is very basic at the moment. Look forward to hear from all your experiences so we can make vector support rock solid in GDC. ;-)https://github.com/D-Programming-GDC/GDC/commit/7ada3d95b8af1b271d82f1ec5208f0b689eb143c#L1R1194the front end yet? Do they work at all? What does the code generated look like?They get passed to the backend as of 2.060 - so looks like the semantic passes now allow them. I've just recently added backend support in GDC -https://github.com/D-Programming-GDC/GDC/commit/9402516e0b07031e841a15849f5dc94ae81dccdc#L4R1201I fixed them again.The codegen looks like so: float4 a = 2; float4 b = [1,2,3,4]; ==> vector(4) float a = { 2.0e+0, 2.0e+0, 2.0e+0, 2.0e+0 }; vector(4) float b = { 1.0e+0, 2.0e+0, 3.0e+0, 4.0e+0 }; ==> movaps .LC0, %xmm0 movaps %xmm0, -24(%ebp) movaps .LC1, %xmm0 movaps %xmm0, -40(%ebp) .align 16 .LC0: .long 1073741824 .long 1073741824 .long 1073741824 .long 1073741824 .align 16 .LC1: .long 1065353216 .long 1073741824 .long 1077936128 .long 1082130432Perfect! I can get on with my unittests :PI didn't say I compiled with optimisations - only -march=native. =)float a = 1, b = 2, c = 3, d = 4; float4 f = [a,b,c,d]; ===> movss -16(%rbp), %xmm0 movss -12(%rbp), %xmm1Errr, that's not fixed...? movss is not the opcode you're looking for. Surely that should produce a single movaps...
Oct 08 2012
On 9 October 2012 00:29, Iain Buclaw <ibuclaw ubuntu.com> wrote:On 8 October 2012 22:18, Manu <turkeyman gmail.com> wrote:Either way, that code is wrong. The prior code was correct (albeit with the redundant store, which I presume would have gone away with optimisation enabled)On 8 October 2012 23:05, Iain Buclaw <ibuclaw ubuntu.com> wrote:usingOn 7 October 2012 13:12, Manu <turkeyman gmail.com> wrote:On 5 October 2012 14:46, Iain Buclaw <ibuclaw ubuntu.com> wrote:On 5 October 2012 11:28, Manu <turkeyman gmail.com> wrote:On 3 October 2012 16:40, Iain Buclaw <ibuclaw ubuntu.com> wrote:On 3 October 2012 02:31, jerro <a a.com> wrote:import core.simd, std.stdio; void main() { float4 a = 1, b = 2; writeln((a + b).array); // WORKS: [3, 3, 3, 3] float4 c = [1, 2, 3, 4]; // ERROR: "Stored value type does // not match pointer operand type!" // [..a bunch of LLVM error code..] float4 c = 0, d = 1; c.array[0] = 4; c.ptr[1] = 4; writeln((c + d).array); // WRONG: [1, 1, 1, 1] }Oh, that doesn't work for me either. I never tried to use those, so I didn't notice that before. This code gives me internal compiler errors with GDC and DMD too (with "float4 c = [1, 2, 3, 4]" commented out). I'minI didn't realise vector literals like that were supported properlyDMD 2.060 and a recent versions of GDC and LDC on 64 bit Linux.Then don't just talk about it, raise a bug - otherwise how do you expect it to get fixed! ( http://www.gdcproject.org/bugzilla ) I've made a note of the error you get with `__vector(float[4]) c = [1,2,3,4];' - That is because vector expressions implementation is very basic at the moment. Look forward to hear from all your experiences so we can make vector support rock solid in GDC. ;-)https://github.com/D-Programming-GDC/GDC/commit/7ada3d95b8af1b271d82f1ec5208f0b689eb143c#L1R1194the front end yet? Do they work at all? What does the code generated look like?They get passed to the backend as of 2.060 - so looks like the semantic passes now allow them. I've just recently added backend support in GDC -https://github.com/D-Programming-GDC/GDC/commit/9402516e0b07031e841a15849f5dc94ae81dccdc#L4R1201I fixed them again.The codegen looks like so: float4 a = 2; float4 b = [1,2,3,4]; ==> vector(4) float a = { 2.0e+0, 2.0e+0, 2.0e+0, 2.0e+0 }; vector(4) float b = { 1.0e+0, 2.0e+0, 3.0e+0, 4.0e+0 }; ==> movaps .LC0, %xmm0 movaps %xmm0, -24(%ebp) movaps .LC1, %xmm0 movaps %xmm0, -40(%ebp) .align 16 .LC0: .long 1073741824 .long 1073741824 .long 1073741824 .long 1073741824 .align 16 .LC1: .long 1065353216 .long 1073741824 .long 1077936128 .long 1082130432Perfect! I can get on with my unittests :PI didn't say I compiled with optimisations - only -march=native. =)float a = 1, b = 2, c = 3, d = 4; float4 f = [a,b,c,d]; ===> movss -16(%rbp), %xmm0 movss -12(%rbp), %xmm1Errr, that's not fixed...? movss is not the opcode you're looking for. Surely that should produce a single movaps...
Oct 08 2012
On 8 August 2012 14:14, F i L <witte2008 gmail.com> wrote:David Nadlinger wrote:I haven't considered/written an SSE2 fallback yet, but I expect some trick using shuf and/or shifts to blend the 2 vectors together will do it.objdump, otool =E2=80=93 depending on your OS.Hey, nice tools. Good to know, thanks! Manu: Here's the disassembly for my benchmark code earlier, isolated between StopWatch .start()/.stop() https://gist.github.com/**3294283 <https://gist.github.com/3294283> Also, I noticed your std.simd.setY() function uses _blendps() op, but DMD's core.simd doesn't support this op (yet? It's there but commented out). Is there an alternative operation I can use for setY() ?
Oct 02 2012
On 8 August 2012 04:45, F i L <witte2008 gmail.com> wrote:Manu wrote:I actually haven't had time to try out the new 2.60 alignment changes in practise yet. As a Win64 D user, I'm stuck with compilers that are forever 3-6 months out of date (2.58). >_< The use cases I required to do this stuff efficiently were definitely agreed though by Walter, and to my knowledge, implemented... so it might be some other subtle details. It's possible that the intrinsic vector code-gen is hard-coded to use unaligned loads too. You might need to assert appropriate alignment, and then issue the movaps intrinsics directly, but I'm sure DMD can be fixed to emit movaps when it detects the vector is aligned >= 16 bytes. [*clip* portability and cross-lane efficiency *clip*]I'm not sure why the performance would suffer when placing it in a struct. I suspect it's because the struct causes the vectors to become unaligned, and that impacts performance a LOT. Walter has recently made some changes to expand the capability of align() to do most of the stuff you expect should be possible, including aligning structs, and propogating alignment from a struct member to its containing struct. This change might actually solve your problems...I've tried all combinations with align() before and inside the struct, with no luck. I'm using DMD 2.060, so unless there's a new syntax I'm unaware of, I don't think it's been adjusted to fix any alignment issues with SIMD stuff. It would be great to be able to wrap float4 into a struct, but for now I've come up with an easy and understandable alternative using SIMD types directly.Well, actually, height maps are one thing that hardware SIMD units aren't intrinsically good at, because they do specifically require component-wise access. That said, there are still lots of interesting possibilities. If you're operating on a height map, for instance, rather than looping over the position vectors, fetching y from it, doing something with y (which I presume involves foreign data?), then putting it back, and repeating over the next vector... Do something like: align(16) float[as-many-as-there-are-verts] height_offsets; foreach(h; height_offsets) { // do some work to generate deltas for each vertex... } Now what you can probably do is unpack them and apply them directly to the vertex stream in a single pass: for(i = 0; i < numVerts; i += 4) { four_heights = loadaps(&height_offsets[i]); float4[4] heights; // do some shuffling/unpacking to result: four_heights.xyzw -> height[0].y, height[1].y, height[2].y, height[3].y (fiddly, but simple enough) vertices[i + 0] += height[0]; vertices[i + 1] += height[1]; vertices[i + 2] += height[2]; vertices[i + 3] += height[3]; } ... I'm sure that could be improved, but you get the idea..? This approach should pipeline well, have reasonably low bandwidth, make good use of registers, and you can see there is no interaction between the FPU and SIMD unit. Disclaimer: I just made that up off the top of my head ;), but that illustrates the kind of approach you would usually take in efficient (and portable) SIMD code. If not, and manipulating components is just bad for performance reasons,Okay, that makes a lot of sense and is inline with what I was reading last night about FPU/SSE assembly code. However I'm also a bit confused. At some point, like in your hightmap example, I'm going to need to do arithmetic work on single vector components. Is there some sort of SSE arithmetic/shuffle instruction which uses "masking" that I should use to isolate and manipulate components?then I've figured out a solution to my original concern. By using this code: property trusted pure nothrow { auto ref x(T:float4)(auto ref T v) { return v.ptr[0]; } auto ref y(T:float4)(auto ref T v) { return v.ptr[1]; } auto ref z(T:float4)(auto ref T v) { return v.ptr[2]; } auto ref w(T:float4)(auto ref T v) { return v.ptr[3]; } void x(T:float4)(ref float4 v, float val) { v.ptr[0] = val; } void y(T:float4)(ref float4 v, float val) { v.ptr[1] = val; } void z(T:float4)(ref float4 v, float val) { v.ptr[2] = val; } void w(T:float4)(ref float4 v, float val) { v.ptr[3] = val; } }This is fine if your vectors are in memory to begin with. But if you're already doing work on them, and they are in registers/local variables, this is the worst thing you can do. It's a generally bad practise, and someone who isn't careful with their usage will produce very slow code (on some platforms).
Oct 02 2012