Add common type conversions
PR Details
This PR adds some useful extensions that are commonly used with external libraries since Numerics has become the standard.
Related Issue
https://github.com/stride3d/stride/pull/2131 this is a great example where something like this could be used.
Motivation and Context
the Bepu branch has this built into the library but its also used in the model importer as well as any library that may need these fast conversions.
Types of changes
- [ ] Docs change / refactoring / dependency upgrade
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
Checklist
- [ ] My change requires a change to the documentation.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [ ] I have built and run the editor to try this change out.
I think if you pass by ref (use in and Unsafe.AsRef) and apply [MethodImpl(MethodImplOptions.AggressiveInlining)], the conversions should be basically free.
I did some quick tests with Inlining and the performance was the same or similar on a few runs. It seems like the compiler does not make any significant changes with or witthout it to the compiled code, I tested with the Bepu library mentioned above.
Unless Im missing something I cant seem to use Unsafe.AsRef for conversion to another type?
Here's a couple of variants with the jitted asm shown: https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABABgAIA5ANRjA2gCZyBec4gRgDpKBXfGFACWYXF1r0mAbgCwAKDLtuAJV4A7DEIFcAwhHwAHIQBtBAZUEA3ETFyy584gGYlSdsx3kA3vPJ/yvv7O5LgYULz05BIMUIyBfj5y/snsLgBmxhDYGOQIaOQAnvbJAL7xAUlBLpxu0UzkAEJ4MAAUGAAWQrhUdbHklgCUrAB85ACqarjYaTBcAIK4ADw0dDGM+b2Mwy2waf0Dxf7lwTXk6lMzUav1AAphOngYbZ3dK5J9gyPkAFQtm98DABklkOfmO1Q4tWufWUMDSzy65F2PWhzE+LFGEwuswWy02G1R22Rg1BFWSAG0ALIwDoQAAmAElDMYWtTaYzmQB5AyaCCTeYAcwFsFwuCElhgDLUxiEallAoGAF1wa4ru9mLC0lKZWoYHSEd1kW81vsvljpjilsamAT1US4ftSSrTptGkIMGBHgaUerTRjxpMLVwGu6HqE8ajbWttiTys7IWqTSGPY8pS1ZeQOojrR8hv7zTNg6HHhH1VGmDGDnHKmCa6lVa6ACq2DBNXCtHNogblRIpfyuqCsfrB5otKt15INWDYADWYwMjfaME5N1wY9JyWIAHYkaSynX41Dfc3QncoGGnp39j3yskB0PLFwzxf17f/FOYLP54vl6vXxOgh3KA92rTcISPE0TwwTUWivQYbwAvx7zYR8YPHPs/A/L8FyXFc13QjDt13cp92SQ9E3qKDNW1WU9Vg114LrXsMOQ4dqOlWj9QIvssLnHDf3wjdAOIutSKOOsTgTJsW2TT1Qno1FryYt8kMUwcUKLFN5O4lJeO/XC/x0zcgJAg8JPAii+ig2TUzUBTfUY5JmL7VjHxs0I0yM99pz4n88P/DD2BMkjQKCBNiBQRofP0gSxwQwLOC4TgAE4WgAIgAEX4fACnIAR2UzCByBgSZeFgcg8HwchEVwGUBXaDBjFy/BoBgJEYAMEUSowbJxTaiA9mwdrsGMcg0nUeghD5NKdP3EogA==
As you can see, the jit tends to inline such a small function regardless of the attribute.
The newly introduced BitCast seems like the best of both world here as the input doesn't need to be passed as ref/in to have improved assembly (see asm for C.TestBitcast).
Ah nice, BitCast looks awesome. I was imagining it like this, which it looks like BitCast creates the same code in a cleaner way.
public static Vector2 ToStride(this in NVector2 v) => Unsafe.As<NVector2, Vector2>(ref Unsafe.AsRef(v));
The in modifier is to prevent pass-by-value copies (which you can see in the asm of TestBase vs TestRef).
as the input doesn't need to be passed as
ref/into have improved assembly (see asm forC.TestBitcast).
The assembly for Bitcast vs BitcastIn is different though. It looks like it's simply better with in (in case the JIT doesn't actually inline it, which is not guaranteed).
C.Bitcast(System.Numerics.Vector2)
L0000: vzeroupper
L0003: vmovq xmm0, rcx
L0008: vmovq rax, xmm0
L000d: ret
C.BitcastIn(System.Numerics.Vector2 ByRef)
L0000: mov rax, [rcx]
L0003: ret