Add long/ulong->float cast helpers #114597
Open
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Resolves #106646
We currently have a few inconsistencies in the way long/ulong to float conversions are done in JIT.
Problem 1
On 64-bit platforms,
long->float
conversions have always been done directly with a native CPU instruction, while on 32-bit,long->float
has always been morphed tolong->double->float
so that it could useCORINFO_HELP_LNG2DBL
.This can lead to different conversion results when the
long->double
conversion rounds in a different direction thanlong->float
would. Ex: sharplab.Similarly,
ulong->float
may yield a different result thanulong->double->float
.Problem 2
In CIL, there is no way to represent a conversion directly from unsigned to float or double. The IL instruction
conv.r.un
specifies unsigned conversion to IL typeF
which is of indeterminate precision. Consequently, managed language compilers emit a pair of instructions for these casts:conv.r.un; conv.r4
orconv.r.un; conv.r8
.The JIT importer has always treated
conv.r.un
asunsigned->double
, so in the case ofconv.r.un; conv.r8
, the second (double->double
) cast is skipped, and the intention of the managed compiler is preserved.conv.r.un; conv.r4
, on the other hand, imports asunsigned->double->float
.As stated above, this could yield a different result than intended for
ulong
.Problem 3
When Arm64 support was added to JIT, as an optimization to take advantage of the fact Arm64 has a direct
ulong->float
conversion instruction, recognition of theulong->double->float
pattern was added to JIT, and the intermediate cast was removed. This led to Arm64 having different cast behavior than all other platforms.#84384 extended the same optimization to x64 in .NET 8, using AVX-512 instructions. This led to further fragmentation of behavior since the presence or absence of AVX-512 could change results.
#111595 unified the behavior on x64 by emulating the direct
ulong->float
cast using SSE instructions.However, this still leaves problem 1 (32-bit still goes through
CORINFO_HELP_ULNG2DBL
) and introduces another: The intermediate double cast may have actually been intentional, and the current optimization removes it without knowing. i.e.conv.r.un; conv.r4
andconv.r.un; conv.r8; conv.r4
are treated the same.The solution
This PR adds JIT helpers to perform
long->float
andulong->float
casts directly for 32-bit platforms.It also modifies JIT to specifically look for the
conv.r.un; conv.r4
sequence and import it asunsigned->float
, and then calls the new helpers as appropriate.Next steps
Because this requires a JIT-EE GUID change, I have broken the solution into two parts.
This part solves problems 1 and 2 by making sure all platforms can consistently recognize and perform the long/ulong->float casts directly.
I will address problem 3 in a followup PR, along with some more cleanup, optimization, and added tests. The current bad optimization also catches some redundant casts that will need to be handled differently, and it will be easier to see the impact of all those changes if the SPMI asmdiff jobs work.