-
Notifications
You must be signed in to change notification settings - Fork 5k
Add long/ulong->float cast helpers #114597
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
/cc @dotnet/jit-contrib |
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
jit-format is failing due to changes from #114525. cc @kunalspathak (fixed in #114603) |
The VM side of the changes LGTM |
@saucecontrol Please merge and resolve conflicts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CC. @dotnet/jit-contrib, @jakobbotsch for secondary review
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.